hledger-forecast 1.5.2 → 2.0.1
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/.rubocop.yml +17 -17
- data/README.md +7 -171
- data/example.journal +14 -14
- data/hledger-forecast.gemspec +1 -1
- data/lib/hledger_forecast/calculator.rb +5 -1
- data/lib/hledger_forecast/cli.rb +5 -18
- data/lib/hledger_forecast/generator.rb +52 -35
- data/lib/hledger_forecast/settings.rb +42 -27
- data/lib/hledger_forecast/summarizer.rb +28 -62
- data/lib/hledger_forecast/summarizer_formatter.rb +4 -4
- data/lib/hledger_forecast/transactions/default.rb +28 -57
- data/lib/hledger_forecast/transactions/trackers.rb +34 -40
- data/lib/hledger_forecast/utilities.rb +14 -0
- data/lib/hledger_forecast/version.rb +1 -1
- data/lib/hledger_forecast.rb +1 -2
- data/spec/cli_spec.rb +3 -12
- data/spec/computed_amounts_spec.rb +11 -22
- data/spec/custom_spec.rb +15 -35
- data/spec/half-yearly_spec.rb +6 -13
- data/spec/monthly_end_date_spec.rb +8 -18
- data/spec/monthly_end_date_transaction_spec.rb +20 -45
- data/spec/monthly_spec.rb +11 -28
- data/spec/once_spec.rb +6 -13
- data/spec/quarterly_spec.rb +5 -12
- data/spec/summarizer_spec.rb +11 -42
- data/spec/track_spec.rb +19 -49
- data/spec/verbose_output_spec.rb +3 -3
- data/spec/yearly_spec.rb +5 -12
- metadata +4 -13
- data/example.yml +0 -98
- data/lib/hledger_forecast/csv_parser.rb +0 -106
- data/spec/csv_and_yml_comparison_spec.rb +0 -32
- data/spec/csv_parser_spec.rb +0 -110
- data/spec/modifier_spec.rb +0 -102
- data/spec/stubs/forecast.yml +0 -19
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: c025630ee2a5d379fcf9e27509c1c5cc5438e5b0a38fcc73c3277fab8e1a6e2e
         | 
| 4 | 
            +
              data.tar.gz: 6d50f7774dbf3331328b9126392bad45aba281b00b4e93eb9060671b982560f6
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 7a21eca09d0f5145a54e4acff809090179cc48e9aa060ed6117c5ef2d17f578aab93b6779396254c3ff0437ff29ecffecf92546622f0dc2f9d12ee61e2794dbd
         | 
| 7 | 
            +
              data.tar.gz: 43509f84789f5dc0700924e22d3137eb4f07a11ee4cbbecef291770c4bdb2bbf83ef63c4aaecc7a87c0a4b77bc5ff29d578dc0e231cf68db85e76fc00f187ec7
         | 
    
        data/.rubocop.yml
    CHANGED
    
    | @@ -4,20 +4,20 @@ AllCops: | |
| 4 4 | 
             
            Layout/LineLength:
         | 
| 5 5 | 
             
              Max: 120
         | 
| 6 6 |  | 
| 7 | 
            -
            Style/StringLiterals:
         | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
            Style/RedundantReturn:
         | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
            Metrics/ClassLength:
         | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
            Metrics/MethodLength:
         | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
            Metrics/AbcSize:
         | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
            Style/NumericPredicate:
         | 
| 23 | 
            -
             | 
| 7 | 
            +
            # Style/StringLiterals:
         | 
| 8 | 
            +
            #   Enabled: false
         | 
| 9 | 
            +
            #
         | 
| 10 | 
            +
            # Style/RedundantReturn:
         | 
| 11 | 
            +
            #   Enabled: false
         | 
| 12 | 
            +
            #
         | 
| 13 | 
            +
            # Metrics/ClassLength:
         | 
| 14 | 
            +
            #   Enabled: False
         | 
| 15 | 
            +
            #
         | 
| 16 | 
            +
            # Metrics/MethodLength:
         | 
| 17 | 
            +
            #   Enabled: False
         | 
| 18 | 
            +
            #
         | 
| 19 | 
            +
            # Metrics/AbcSize:
         | 
| 20 | 
            +
            #   Enabled: False
         | 
| 21 | 
            +
            #
         | 
| 22 | 
            +
            # Style/NumericPredicate:
         | 
| 23 | 
            +
            #   Enabled: False
         | 
    
        data/README.md
    CHANGED
    
    | @@ -11,13 +11,13 @@ | |
| 11 11 | 
             
            <a href="https://github.com/olimorris/hledger-forecast/actions/workflows/test.yml"><img src="https://img.shields.io/github/actions/workflow/status/olimorris/hledger-forecast/test.yml?branch=main&label=tests&style=for-the-badge"></a>
         | 
| 12 12 | 
             
            </p>
         | 
| 13 13 |  | 
| 14 | 
            -
            **"Improved", you say?** Using a _CSV_  | 
| 14 | 
            +
            **"Improved", you say?** Using a _CSV_ file, forecasts can be quickly generated into a _journal_ file ready to be fed into [hledger](https://github.com/simonmichael/hledger). **A 16 line [CSV file](https://github.com/olimorris/hledger-forecast/blob/main/example.csv) can generate a 46 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 | 
            -
            - :rocket: Uses a simple CSV  | 
| 20 | 
            +
            - :rocket: Uses a simple CSV file to generate forecasts which can be used with hledger
         | 
| 21 21 | 
             
            - :calendar: Can smartly track forecasts against your bank statement
         | 
| 22 22 | 
             
            - :dollar: Can automatically apply modifiers such as inflation/deflation to forecasts
         | 
| 23 23 | 
             
            - :mag: Enables the use of maths in your forecasts (for amounts and dates)
         | 
| @@ -62,13 +62,13 @@ The available options are: | |
| 62 62 |  | 
| 63 63 | 
             
            ### Generate command
         | 
| 64 64 |  | 
| 65 | 
            -
            The `hledger-forecast generate` command will generate a forecast _from_ a `CSV`  | 
| 65 | 
            +
            The `hledger-forecast generate` command will generate a forecast _from_ a `CSV` 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.
         | 
| 66 66 |  | 
| 67 67 | 
             
            The available options are:
         | 
| 68 68 |  | 
| 69 69 | 
             
                Usage: hledger-forecast generate [options]
         | 
| 70 70 |  | 
| 71 | 
            -
                  -f, --forecast FILE              The path to the FORECAST CSV | 
| 71 | 
            +
                  -f, --forecast FILE              The path to the FORECAST CSV file to generate from
         | 
| 72 72 | 
             
                  -o, --output-file FILE           The path to the OUTPUT file to create
         | 
| 73 73 | 
             
                  -t, --transaction FILE           The path to the TRANSACTION journal file
         | 
| 74 74 | 
             
                  -v, --verbose                    Don't group transactions by type in the output file
         | 
| @@ -78,7 +78,7 @@ The available options are: | |
| 78 78 |  | 
| 79 79 | 
             
            > **Note**: For the tracking of transactions you need to include the `-t` flag
         | 
| 80 80 |  | 
| 81 | 
            -
            Running the command with no options will assume a `forecast. | 
| 81 | 
            +
            Running the command with no options will assume a `forecast.csv` file exists.
         | 
| 82 82 |  | 
| 83 83 | 
             
            ### Using with hledger
         | 
| 84 84 |  | 
| @@ -100,7 +100,7 @@ The available options are: | |
| 100 100 |  | 
| 101 101 | 
             
                Usage: hledger-forecast summarize [options]
         | 
| 102 102 |  | 
| 103 | 
            -
                -f, --forecast FILE              The path to the FORECAST CSV | 
| 103 | 
            +
                -f, --forecast FILE              The path to the FORECAST CSV file to summarize
         | 
| 104 104 | 
             
                -r, --roll-up PERIOD             The period to roll-up your forecasts into. One of:
         | 
| 105 105 | 
             
                                                 [yearly], [half-yearly], [quarterly], [monthly], [weekly], [daily]
         | 
| 106 106 | 
             
                -v, --verbose                    Show additional information in the summary
         | 
| @@ -138,7 +138,7 @@ The _CSV_ file _should_ contain a header row with the following columns: | |
| 138 138 | 
             
            - `summary_exclude` - (boolean) _(optional)_ - Exclude the transaction from the summarizer?
         | 
| 139 139 | 
             
            - `track` - (boolean) _(optional)_ - Track the transaction against your confirmed transactions?
         | 
| 140 140 |  | 
| 141 | 
            -
            ###  | 
| 141 | 
            +
            ### Example forecast
         | 
| 142 142 |  | 
| 143 143 | 
             
            Putting it together, we end up with a CSV file like:
         | 
| 144 144 |  | 
| @@ -198,157 +198,6 @@ settings,show_symbol,true,,,,,,,, | |
| 198 198 | 
             
            settings,thousands_separator,true,,,,,,,,
         | 
| 199 199 | 
             
            ```
         | 
| 200 200 |  | 
| 201 | 
            -
            ### An example YML forecast
         | 
| 202 | 
            -
             | 
| 203 | 
            -
            > **Note**: The app uses `yml` in place of `yaml` by default
         | 
| 204 | 
            -
             | 
| 205 | 
            -
            Taking the _CSV_ example above and applying it to a _YML_ file:
         | 
| 206 | 
            -
             | 
| 207 | 
            -
            ```yml
         | 
| 208 | 
            -
            monthly:
         | 
| 209 | 
            -
              - account: "Assets:Bank"
         | 
| 210 | 
            -
                from: "2023-03-01"
         | 
| 211 | 
            -
                transactions:
         | 
| 212 | 
            -
                  - amount: -3500
         | 
| 213 | 
            -
                    category: "Income:Salary"
         | 
| 214 | 
            -
                    description: Salary
         | 
| 215 | 
            -
                  - amount: 2000
         | 
| 216 | 
            -
                    category: "Expenses:Mortgage"
         | 
| 217 | 
            -
                    description: Mortgage
         | 
| 218 | 
            -
                    to: "2025-01-01"
         | 
| 219 | 
            -
                  - amount: 175
         | 
| 220 | 
            -
                    category: "Expenses:Bills"
         | 
| 221 | 
            -
                    description: Bills
         | 
| 222 | 
            -
                  - amount: 500
         | 
| 223 | 
            -
                    category: "Expenses:Food"
         | 
| 224 | 
            -
                    description: Food
         | 
| 225 | 
            -
                  - amount: "=5000/24"
         | 
| 226 | 
            -
                    category: "Expenses:House"
         | 
| 227 | 
            -
                    description: New Kitchen
         | 
| 228 | 
            -
                  - amount: 125
         | 
| 229 | 
            -
                    category: "Expenses:Holiday"
         | 
| 230 | 
            -
                    description: Holiday
         | 
| 231 | 
            -
                    to: "=12"
         | 
| 232 | 
            -
              - account: "Assets:Bank"
         | 
| 233 | 
            -
                from: "2023-03-01"
         | 
| 234 | 
            -
                to: "2025-01-01"
         | 
| 235 | 
            -
                transactions:
         | 
| 236 | 
            -
                  - amount: 300
         | 
| 237 | 
            -
                    category: "Assets:Savings"
         | 
| 238 | 
            -
                    description: "Rainy day fund"
         | 
| 239 | 
            -
              - account: "Assets:Pension"
         | 
| 240 | 
            -
                from: "2024-01-01"
         | 
| 241 | 
            -
                transactions:
         | 
| 242 | 
            -
                  - amount: -500
         | 
| 243 | 
            -
                    category: "Income:Pension"
         | 
| 244 | 
            -
                    description: Pension draw down
         | 
| 245 | 
            -
             | 
| 246 | 
            -
            quarterly:
         | 
| 247 | 
            -
              - account: "Assets:Bank"
         | 
| 248 | 
            -
                from: "2023-04-01"
         | 
| 249 | 
            -
                transactions:
         | 
| 250 | 
            -
                  - amount: -1000.00
         | 
| 251 | 
            -
                    category: "Income:Bonus"
         | 
| 252 | 
            -
                    description: Quarterly bonus
         | 
| 253 | 
            -
             | 
| 254 | 
            -
            half-yearly:
         | 
| 255 | 
            -
              - account: "Assets:Bank"
         | 
| 256 | 
            -
                from: "2023-04-01"
         | 
| 257 | 
            -
                transactions:
         | 
| 258 | 
            -
                  - amount: 500
         | 
| 259 | 
            -
                    category: "Expenses:Holiday"
         | 
| 260 | 
            -
                    description: Top up holiday funds
         | 
| 261 | 
            -
             | 
| 262 | 
            -
            yearly:
         | 
| 263 | 
            -
              - account: "Assets:Bank"
         | 
| 264 | 
            -
                from: "2023-04-01"
         | 
| 265 | 
            -
                transactions:
         | 
| 266 | 
            -
                  - amount: -2000.00
         | 
| 267 | 
            -
                    category: "Income:Bonus"
         | 
| 268 | 
            -
                    description: Annual Bonus
         | 
| 269 | 
            -
             | 
| 270 | 
            -
            once:
         | 
| 271 | 
            -
              - account: "Assets:Bank"
         | 
| 272 | 
            -
                from: "2023-03-05"
         | 
| 273 | 
            -
                transactions:
         | 
| 274 | 
            -
                  - amount: -3000
         | 
| 275 | 
            -
                    category: "Expenses:Shopping"
         | 
| 276 | 
            -
                    description: Refund for that damn laptop
         | 
| 277 | 
            -
                    summary_exclude: true
         | 
| 278 | 
            -
                    track: true
         | 
| 279 | 
            -
             | 
| 280 | 
            -
            custom:
         | 
| 281 | 
            -
              - account: "Assets:Bank"
         | 
| 282 | 
            -
                from: "2023-03-01"
         | 
| 283 | 
            -
                transactions:
         | 
| 284 | 
            -
                  - amount: 80
         | 
| 285 | 
            -
                    category: "Expenses:Personal Care"
         | 
| 286 | 
            -
                    description: Hair and beauty
         | 
| 287 | 
            -
                    frequency: "every 2 weeks"
         | 
| 288 | 
            -
                    roll-up: 26
         | 
| 289 | 
            -
                  - amount: 30
         | 
| 290 | 
            -
                    category: "Expenses:General Expenses"
         | 
| 291 | 
            -
                    description: Misc expenses
         | 
| 292 | 
            -
                    frequency: "every 5 weeks"
         | 
| 293 | 
            -
                    roll-up: 10.4
         | 
| 294 | 
            -
             | 
| 295 | 
            -
            settings:
         | 
| 296 | 
            -
              currency: USD
         | 
| 297 | 
            -
            ```
         | 
| 298 | 
            -
             | 
| 299 | 
            -
            #### Modifiers
         | 
| 300 | 
            -
             | 
| 301 | 
            -
            > **Note**: For modifiers to be included in your hledger reporting, use the `--auto` flag
         | 
| 302 | 
            -
             | 
| 303 | 
            -
            Currently, a YML forecast allows a user to include forecasted % uplifts or downshifts:
         | 
| 304 | 
            -
             | 
| 305 | 
            -
            ```yml
         | 
| 306 | 
            -
            monthly:
         | 
| 307 | 
            -
              - account: "Assets:Bank"
         | 
| 308 | 
            -
                from: "2023-03-01"
         | 
| 309 | 
            -
                transactions:
         | 
| 310 | 
            -
                  - amount: 500
         | 
| 311 | 
            -
                    category: "Expenses:Food"
         | 
| 312 | 
            -
                    description: Food
         | 
| 313 | 
            -
                    modifiers:
         | 
| 314 | 
            -
                      - amount: 0.02
         | 
| 315 | 
            -
                        description: "Inflation"
         | 
| 316 | 
            -
                        from: "2024-01-01"
         | 
| 317 | 
            -
                        to: "2024-12-31"
         | 
| 318 | 
            -
                      - amount: 0.05
         | 
| 319 | 
            -
                        description: "Inflation"
         | 
| 320 | 
            -
                        from: "2025-01-01"
         | 
| 321 | 
            -
                        to: "2025-12-31"
         | 
| 322 | 
            -
            ```
         | 
| 323 | 
            -
             | 
| 324 | 
            -
            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%.
         | 
| 325 | 
            -
             | 
| 326 | 
            -
            #### Additional YML features
         | 
| 327 | 
            -
             | 
| 328 | 
            -
            Dates in a YML file can be constrained by the `to` date in two ways:
         | 
| 329 | 
            -
             | 
| 330 | 
            -
            ```yml
         | 
| 331 | 
            -
            monthly:
         | 
| 332 | 
            -
              - account: "Assets:Bank"
         | 
| 333 | 
            -
                from: "2023-03-01"
         | 
| 334 | 
            -
                to: "2025-01-01"
         | 
| 335 | 
            -
                transactions:
         | 
| 336 | 
            -
                  # details omitted for brevity
         | 
| 337 | 
            -
            ```
         | 
| 338 | 
            -
             | 
| 339 | 
            -
            or:
         | 
| 340 | 
            -
             | 
| 341 | 
            -
            ```yml
         | 
| 342 | 
            -
            monthly:
         | 
| 343 | 
            -
              - account: "Assets:Bank"
         | 
| 344 | 
            -
                from: "2023-03-01"
         | 
| 345 | 
            -
                transactions:
         | 
| 346 | 
            -
                  - amount: 2000
         | 
| 347 | 
            -
                    category: "Expenses:Mortgage"
         | 
| 348 | 
            -
                    description: Mortgage
         | 
| 349 | 
            -
                    to: "2025-01-01"
         | 
| 350 | 
            -
            ```
         | 
| 351 | 
            -
             | 
| 352 201 | 
             
            ### Tracking
         | 
| 353 202 |  | 
| 354 203 | 
             
            > **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
         | 
| @@ -362,19 +211,6 @@ type,frequency,account,from,to,description,category,amount,roll-up,summary_exclu | |
| 362 211 | 
             
            once,,Assets:Bank,2023-03-05,,Refund for that damn laptop,Expenses:Shopping,-3000,,,TRUE
         | 
| 363 212 | 
             
            ```
         | 
| 364 213 |  | 
| 365 | 
            -
            Or:
         | 
| 366 | 
            -
             | 
| 367 | 
            -
            ```yml
         | 
| 368 | 
            -
            once:
         | 
| 369 | 
            -
              - account: "Assets:Bank"
         | 
| 370 | 
            -
                from: "2023-03-05"
         | 
| 371 | 
            -
                transactions:
         | 
| 372 | 
            -
                  - amount: -3000
         | 
| 373 | 
            -
                    category: "Expenses:Shopping"
         | 
| 374 | 
            -
                    description: Refund for that damn laptop
         | 
| 375 | 
            -
                    track: true
         | 
| 376 | 
            -
            ```
         | 
| 377 | 
            -
             | 
| 378 214 | 
             
            > **Note**: This feature has been designed to work with `once` transaction types only
         | 
| 379 215 |  | 
| 380 216 | 
             
            To use this feature, ensure you pass a filepath to the `-t` flag, such as:
         | 
    
        data/example.journal
    CHANGED
    
    | @@ -1,47 +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 | 
| 4 | 
            -
                Expenses:Food                $500.00 | 
| 5 | 
            -
                Expenses:House               $208.33 | 
| 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 | 
| 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 | 
| 13 | 
            +
                Expenses:Holiday             $125.00      ;  Holiday
         | 
| 14 14 | 
             
                Assets:Bank
         | 
| 15 15 |  | 
| 16 16 | 
             
            ~ monthly from 2023-03-01 to 2025-03-01  * Rainy day fund
         | 
| 17 | 
            -
                Assets:Savings               $300.00 | 
| 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 | 
| 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 | 
| 29 | 
            +
                Expenses:Holiday             $500.00      ;  Top up holiday funds
         | 
| 30 30 | 
             
                Assets:Bank
         | 
| 31 31 |  | 
| 32 32 | 
             
            ~ yearly from 2023-04-01  * Annual bonus
         | 
| 33 | 
            -
                Income:Bonus                 $-2,000.00;  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 | 
| 37 | 
            +
                Expenses:Personal Care       $80.00       ;  Hair and beauty
         | 
| 38 38 | 
             
                Assets:Bank
         | 
| 39 39 |  | 
| 40 40 | 
             
            ~ every 5 weeks from 2023-03-01  * Misc expenses
         | 
| 41 | 
            -
                Expenses:General Expenses    $30.00 | 
| 41 | 
            +
                Expenses:General Expenses    $30.00       ;  Misc expenses
         | 
| 42 42 | 
             
                Assets:Bank
         | 
| 43 43 |  | 
| 44 | 
            -
            ~  | 
| 44 | 
            +
            ~ 2024-01-01  * [TRACKED] Refund for that damn laptop
         | 
| 45 45 | 
             
                Expenses:Shopping            $-3,000.00;  Refund for that damn laptop
         | 
| 46 46 | 
             
                Assets:Bank
         | 
| 47 47 |  | 
    
        data/hledger-forecast.gemspec
    CHANGED
    
    | @@ -9,7 +9,7 @@ Gem::Specification.new do |s| | |
| 9 9 | 
             
              s.version     = HledgerForecast::VERSION
         | 
| 10 10 | 
             
              s.authors     = ['Oli Morris']
         | 
| 11 11 | 
             
              s.summary     = "An extended wrapper around hledger's forecasting functionality"
         | 
| 12 | 
            -
              s.description = 'Use a CSV  | 
| 12 | 
            +
              s.description = 'Use a CSV file for improved forecasting with hledger'
         | 
| 13 13 | 
             
              s.email       = 'olimorris@users.noreply.github.com'
         | 
| 14 14 | 
             
              s.homepage    = 'https://github.com/olimorris/hledger-forecast'
         | 
| 15 15 | 
             
              s.license     = 'MIT'
         | 
| @@ -12,7 +12,11 @@ module HledgerForecast | |
| 12 12 | 
             
                end
         | 
| 13 13 |  | 
| 14 14 | 
             
                def evaluate_date(from, to)
         | 
| 15 | 
            -
                   | 
| 15 | 
            +
                  if to[0] != "="
         | 
| 16 | 
            +
                    return to if to.is_a?(Date)
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    return Date.parse(to)
         | 
| 19 | 
            +
                  end
         | 
| 16 20 |  | 
| 17 21 | 
             
                  # Subtract a day from the final date
         | 
| 18 22 | 
             
                  (from >> @calculator.evaluate(to.slice(1..-1))) - 1
         | 
    
        data/lib/hledger_forecast/cli.rb
    CHANGED
    
    | @@ -79,16 +79,9 @@ module HledgerForecast | |
| 79 79 | 
             
                    opts.separator ""
         | 
| 80 80 |  | 
| 81 81 | 
             
                    opts.on("-f", "--forecast FILE",
         | 
| 82 | 
            -
                            "The path to the FORECAST csv | 
| 82 | 
            +
                            "The path to the FORECAST csv file to generate from") do |file|
         | 
| 83 83 | 
             
                      options[:forecast_file] = file
         | 
| 84 | 
            -
             | 
| 85 | 
            -
                      options[:file_type] = if File.extname(file) == '.csv'
         | 
| 86 | 
            -
                                              "csv"
         | 
| 87 | 
            -
                                            else
         | 
| 88 | 
            -
                                              "yml"
         | 
| 89 | 
            -
                                            end
         | 
| 90 | 
            -
             | 
| 91 | 
            -
                      options[:output_file] ||= file.sub(options[:file_type], 'journal')
         | 
| 84 | 
            +
                      options[:output_file] ||= file.sub('csv', 'journal')
         | 
| 92 85 | 
             
                    end
         | 
| 93 86 |  | 
| 94 87 | 
             
                    opts.on("-o", "--output-file FILE",
         | 
| @@ -102,7 +95,7 @@ module HledgerForecast | |
| 102 95 | 
             
                    end
         | 
| 103 96 |  | 
| 104 97 | 
             
                    opts.on("-v", "--verbose",
         | 
| 105 | 
            -
                            " | 
| 98 | 
            +
                            "Do not group transactions in the output file") do
         | 
| 106 99 | 
             
                      options[:verbose] = true
         | 
| 107 100 | 
             
                    end
         | 
| 108 101 |  | 
| @@ -112,7 +105,7 @@ module HledgerForecast | |
| 112 105 | 
             
                    end
         | 
| 113 106 |  | 
| 114 107 | 
             
                    opts.on("--no-track",
         | 
| 115 | 
            -
                            " | 
| 108 | 
            +
                            "Do not track any transactions") do
         | 
| 116 109 | 
             
                      options[:no_track] = true
         | 
| 117 110 | 
             
                    end
         | 
| 118 111 |  | 
| @@ -150,12 +143,7 @@ module HledgerForecast | |
| 150 143 | 
             
                    opts.separator ""
         | 
| 151 144 |  | 
| 152 145 | 
             
                    opts.on("-f", "--forecast FILE",
         | 
| 153 | 
            -
                            "The path to the FORECAST csv | 
| 154 | 
            -
                      options[:file_type] = if File.extname(file) == '.csv'
         | 
| 155 | 
            -
                                              "csv"
         | 
| 156 | 
            -
                                            else
         | 
| 157 | 
            -
                                              "yml"
         | 
| 158 | 
            -
                                            end
         | 
| 146 | 
            +
                            "The path to the FORECAST csv file to summarize") do |file|
         | 
| 159 147 | 
             
                      options[:forecast_file] = file
         | 
| 160 148 | 
             
                    end
         | 
| 161 149 |  | 
| @@ -240,7 +228,6 @@ module HledgerForecast | |
| 240 228 | 
             
                  forecast = File.read(options[:forecast_file])
         | 
| 241 229 |  | 
| 242 230 | 
             
                  begin
         | 
| 243 | 
            -
                    forecast = HledgerForecast::CSVParser.parse(forecast) if options[:file_type] == "csv"
         | 
| 244 231 | 
             
                    transactions = Generator.generate(forecast, options)
         | 
| 245 232 | 
             
                  rescue StandardError => e
         | 
| 246 233 | 
             
                    puts "An error occurred while generating transactions: #{e.message}"
         | 
| @@ -6,61 +6,78 @@ module HledgerForecast | |
| 6 6 | 
             
                end
         | 
| 7 7 |  | 
| 8 8 | 
             
                def generate(config, cli_options = nil)
         | 
| 9 | 
            -
                  forecast =  | 
| 9 | 
            +
                  forecast = CSV.parse(config, headers: true)
         | 
| 10 10 | 
             
                  @settings = Settings.config(forecast, cli_options)
         | 
| 11 11 |  | 
| 12 | 
            -
                   | 
| 13 | 
            -
                  forecast.each do | | 
| 14 | 
            -
                    next if  | 
| 12 | 
            +
                  processed = []
         | 
| 13 | 
            +
                  forecast.each do |row|
         | 
| 14 | 
            +
                    next if row['type'] == "settings"
         | 
| 15 15 |  | 
| 16 | 
            -
                     | 
| 17 | 
            -
             | 
| 16 | 
            +
                    processed.push(process_forecast(row))
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  unless @settings[:verbose]
         | 
| 20 | 
            +
                    processed = processed.group_by do |row|
         | 
| 21 | 
            +
                      [row[:type], row[:frequency], row[:from], row[:to], row[:account], row[:track]]
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    processed = processed.map do |(type, frequency, from, to, account, track), transactions|
         | 
| 25 | 
            +
                      {
         | 
| 26 | 
            +
                        type: type,
         | 
| 27 | 
            +
                        frequency: frequency,
         | 
| 28 | 
            +
                        from: from,
         | 
| 29 | 
            +
                        to: to,
         | 
| 30 | 
            +
                        account: account,
         | 
| 31 | 
            +
                        track: track || false,
         | 
| 32 | 
            +
                        transactions: transactions
         | 
| 33 | 
            +
                      }
         | 
| 18 34 | 
             
                    end
         | 
| 19 35 | 
             
                  end
         | 
| 20 36 |  | 
| 21 37 | 
             
                  Formatter.output_to_ledger(
         | 
| 22 | 
            -
                    Transactions::Default.generate( | 
| 23 | 
            -
                    Transactions::Trackers.generate( | 
| 24 | 
            -
                    Transactions::Modifiers.generate(output, @settings)
         | 
| 38 | 
            +
                    Transactions::Default.generate(processed, @settings),
         | 
| 39 | 
            +
                    Transactions::Trackers.generate(processed, @settings)
         | 
| 25 40 | 
             
                  )
         | 
| 26 41 | 
             
                end
         | 
| 27 42 |  | 
| 28 43 | 
             
                private
         | 
| 29 44 |  | 
| 30 | 
            -
                def  | 
| 31 | 
            -
                   | 
| 45 | 
            +
                def process_forecast(row)
         | 
| 46 | 
            +
                  row['amount'] = Utilities.convert_amount(row['amount'])
         | 
| 32 47 |  | 
| 33 | 
            -
                   | 
| 34 | 
            -
                     | 
| 35 | 
            -
                     | 
| 36 | 
            -
                     | 
| 37 | 
            -
                     | 
| 38 | 
            -
                     | 
| 39 | 
            -
                     | 
| 48 | 
            +
                  {
         | 
| 49 | 
            +
                    type: row['type'],
         | 
| 50 | 
            +
                    frequency: row['frequency'] || nil,
         | 
| 51 | 
            +
                    account: row['account'],
         | 
| 52 | 
            +
                    from: Date.parse(row['from']),
         | 
| 53 | 
            +
                    to: row['to'] ? Calculator.new.evaluate_date(Date.parse(row['from']), row['to']) : nil,
         | 
| 54 | 
            +
                    description: row['description'],
         | 
| 55 | 
            +
                    category: row['category'],
         | 
| 56 | 
            +
                    amount: Formatter.format_money(Calculator.new.evaluate(row['amount']), @settings),
         | 
| 57 | 
            +
                    track: Transactions::Trackers.track?(row, @settings) ? true : false
         | 
| 40 58 | 
             
                  }
         | 
| 59 | 
            +
                end
         | 
| 41 60 |  | 
| 42 | 
            -
             | 
| 61 | 
            +
                def transform_data(data)
         | 
| 62 | 
            +
                  transformed_data = []
         | 
| 43 63 |  | 
| 44 | 
            -
                   | 
| 45 | 
            -
                     | 
| 46 | 
            -
                    item.merge(transactions: transactions)
         | 
| 47 | 
            -
                  end
         | 
| 48 | 
            -
                end
         | 
| 64 | 
            +
                  data.each do |group_key, transactions|
         | 
| 65 | 
            +
                    next if group_key == "settings"
         | 
| 49 66 |  | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
                     | 
| 53 | 
            -
                       | 
| 54 | 
            -
                       | 
| 55 | 
            -
                       | 
| 56 | 
            -
                       | 
| 57 | 
            -
                       | 
| 58 | 
            -
                      track: Transactions::Trackers.track?(t, block, @settings) ? true : false,
         | 
| 59 | 
            -
                      frequency: t['frequency'] || nil
         | 
| 67 | 
            +
                    split_keys = group_key.split("@@")
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                    group_info = {
         | 
| 70 | 
            +
                      type: split_keys[0],
         | 
| 71 | 
            +
                      from: split_keys[1],
         | 
| 72 | 
            +
                      to: split_keys[2],
         | 
| 73 | 
            +
                      account: split_keys[3],
         | 
| 74 | 
            +
                      transactions: transactions
         | 
| 60 75 | 
             
                    }
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                    transformed_data << group_info
         | 
| 61 78 | 
             
                  end
         | 
| 62 79 |  | 
| 63 | 
            -
                   | 
| 80 | 
            +
                  transformed_data
         | 
| 64 81 | 
             
                end
         | 
| 65 82 | 
             
              end
         | 
| 66 83 | 
             
            end
         | 
| @@ -1,41 +1,56 @@ | |
| 1 1 | 
             
            module HledgerForecast
         | 
| 2 | 
            -
              # Set the options from a user's  | 
| 2 | 
            +
              # Set the options from a user's config
         | 
| 3 3 | 
             
              class Settings
         | 
| 4 4 | 
             
                def self.config(forecast, cli_options)
         | 
| 5 5 | 
             
                  settings = {}
         | 
| 6 | 
            +
                  settings[:max_amount] = 0
         | 
| 7 | 
            +
                  settings[:max_category] = 0
         | 
| 6 8 |  | 
| 7 | 
            -
                   | 
| 8 | 
            -
             | 
| 9 | 
            +
                  forecast.each do |row|
         | 
| 10 | 
            +
                    if row['type'] != 'settings'
         | 
| 11 | 
            +
                      category_length = row['category'].length
         | 
| 12 | 
            +
                      settings[:max_category] = category_length if category_length > settings[:max_category]
         | 
| 9 13 |  | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            +
                      amount = if row['amount'].is_a?(Integer) || row['amount'].is_a?(Float)
         | 
| 15 | 
            +
                                 ((row['amount'] + 3) * 100).to_s
         | 
| 16 | 
            +
                               else
         | 
| 17 | 
            +
                                 row['amount'].to_s
         | 
| 18 | 
            +
                               end
         | 
| 14 19 |  | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
                  settings
         | 
| 18 | 
            -
                end
         | 
| 20 | 
            +
                      settings[:max_amount] = amount.length if amount.length > settings[:max_amount]
         | 
| 21 | 
            +
                    end
         | 
| 19 22 |  | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
                       | 
| 23 | 
            +
                    if row['type'] == 'settings'
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                      settings[:currency] = if row['frequency'] == "currency"
         | 
| 26 | 
            +
                                              row['account']
         | 
| 27 | 
            +
                                            else
         | 
| 28 | 
            +
                                              "USD"
         | 
| 29 | 
            +
                                            end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                      settings[:show_symbol] = if row['frequency'] == "show_symbol"
         | 
| 32 | 
            +
                                                 row['account']
         | 
| 33 | 
            +
                                               else
         | 
| 34 | 
            +
                                                 true
         | 
| 35 | 
            +
                                               end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                      settings[:sign_before_symbol] = if row['frequency'] == "sign_before_symbol"
         | 
| 38 | 
            +
                                                        row['account']
         | 
| 39 | 
            +
                                                      else
         | 
| 40 | 
            +
                                                        false
         | 
| 41 | 
            +
                                                      end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                      settings[:thousands_separator] = if row['frequency'] == "thousands_separator"
         | 
| 44 | 
            +
                                                         row['account']
         | 
| 45 | 
            +
                                                       else
         | 
| 46 | 
            +
                                                         ","
         | 
| 47 | 
            +
                                                       end
         | 
| 35 48 | 
             
                    end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    settings.merge!(cli_options) if cli_options
         | 
| 36 51 | 
             
                  end
         | 
| 37 52 |  | 
| 38 | 
            -
                   | 
| 53 | 
            +
                  settings
         | 
| 39 54 | 
             
                end
         | 
| 40 55 | 
             
              end
         | 
| 41 56 | 
             
            end
         |