teri 0.5.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 +7 -0
- data/LICENSE.txt +25 -0
- data/README.md +285 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/teri +5 -0
- data/lib/teri/accounting.rb +310 -0
- data/lib/teri/ai_integration.rb +109 -0
- data/lib/teri/category_manager.rb +92 -0
- data/lib/teri/cli.rb +71 -0
- data/lib/teri/ledger.rb +332 -0
- data/lib/teri/openai_client.rb +229 -0
- data/lib/teri/report_generator.rb +258 -0
- data/lib/teri/transaction.rb +399 -0
- data/lib/teri/transaction_coder.rb +338 -0
- data/lib/teri/version.rb +3 -0
- data/lib/teri.rb +10 -0
- metadata +105 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: dfc371293c931614edbc46daf5d3e25c19b4d7d4199a0119c6b8d6db3a6da8df
|
4
|
+
data.tar.gz: 1042ecd7e4c4ed6a0f0966311ada34fb81c341f6d3f80ef66f06fc7b39d530f0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: acd83189eed624a8e04f387873cf73650dae5a9eb68faeb97d2baff708974027cd6fcc1e200c5e2d3fbb9d21144982e0791c358274f31f97ca5d3df6c6f2085f
|
7
|
+
data.tar.gz: b7c4ca31d5b59e8e83612c2f0ca9578c9f52da017e07cc6856b821179fdbcdcb32aba59415e150d9b572fbdd8138edccff43209808fa005ba4f4e97b6929fed8
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
PROPRIETARY LICENSE
|
2
|
+
|
3
|
+
Copyright (c) 2025 Jonathan Siegel. All Rights Reserved.
|
4
|
+
|
5
|
+
This software and associated documentation files (the "Software") are the proprietary and confidential property of Jonathan Siegel. The Software is protected by copyright laws and international treaty provisions.
|
6
|
+
|
7
|
+
Unauthorized reproduction, distribution, modification, or use of this Software, in whole or in part, is strictly prohibited without the express written permission of Jonathan Siegel.
|
8
|
+
|
9
|
+
LIMITED LICENSE:
|
10
|
+
Subject to the terms and conditions of this License, Jonathan Siegel grants you a non-transferable, non-exclusive, limited license to use the Software solely for your personal or internal business purposes. You may not:
|
11
|
+
|
12
|
+
1. Modify, adapt, alter, translate, or create derivative works of the Software;
|
13
|
+
2. Reverse engineer, decompile, disassemble, or attempt to derive the source code of the Software;
|
14
|
+
3. Rent, lease, loan, sell, sublicense, distribute, transmit, or otherwise transfer rights to the Software;
|
15
|
+
4. Remove or alter any proprietary notices, labels, or marks on the Software;
|
16
|
+
5. Use the Software for commercial purposes without a separate written agreement.
|
17
|
+
|
18
|
+
TERMINATION:
|
19
|
+
This License is effective until terminated. Your rights under this License will terminate automatically without notice if you fail to comply with any of its terms and conditions.
|
20
|
+
|
21
|
+
NO WARRANTY:
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. JONATHAN SIEGEL DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|
23
|
+
|
24
|
+
LIMITATION OF LIABILITY:
|
25
|
+
IN NO EVENT SHALL JONATHAN SIEGEL BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES WHATSOEVER ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,285 @@
|
|
1
|
+
# Teri
|
2
|
+
|
3
|
+
Teri is a double-entry accounting tool for personal banking. It helps manage banking transactions in ledger format, allowing you to code transactions, generate balance sheets, and income statements.
|
4
|
+
|
5
|
+
## Understanding Debits and Credits in Double-Entry Accounting
|
6
|
+
|
7
|
+
In double-entry accounting, every transaction is recorded in two accounts: one with a **debit (Dr.)** and one with a **credit (Cr.)**. Debits and credits reflect opposite sides of a transaction, ensuring every transaction stays balanced.
|
8
|
+
|
9
|
+
### Definitions:
|
10
|
+
|
11
|
+
- **Debit (Dr.)**: Represents the **left side** of an accounting entry.
|
12
|
+
- **Credit (Cr.)**: Represents the **right side** of an accounting entry.
|
13
|
+
|
14
|
+
**Important:**
|
15
|
+
**Debits must always equal credits** for every transaction.
|
16
|
+
|
17
|
+
---
|
18
|
+
|
19
|
+
### Impact of Debits and Credits by Account Type:
|
20
|
+
|
21
|
+
| Account Type | Debit (Dr.) | Credit (Cr.) |
|
22
|
+
|----------------------|-------------------------|-------------------------|
|
23
|
+
| **Assets** | **Increase ⬆️** | Decrease ⬇️ |
|
24
|
+
| **Liabilities** | Decrease ⬇️ | **Increase ⬆️** |
|
25
|
+
| **Equity** | Decrease ⬇️ | **Increase ⬆️** |
|
26
|
+
| **Income (Revenue)** | Decrease ⬇️ | **Increase ⬆️** |
|
27
|
+
| **Expenses** | **Increase ⬆️** | Decrease ⬇️ |
|
28
|
+
|
29
|
+
**Mnemonic** (to remember easily): **DEALER**
|
30
|
+
|
31
|
+
- **D**ividends, **E**xpenses, **A**ssets increase with **Debit**
|
32
|
+
- **L**iabilities, **E**quity, **R**evenue increase with **Credit**
|
33
|
+
|
34
|
+
---
|
35
|
+
|
36
|
+
### Example Scenario:
|
37
|
+
|
38
|
+
1. **Invest $50,000 cash into your company**:
|
39
|
+
- **Debit**: `Assets:Cash` **$50,000** (Cash increases)
|
40
|
+
- **Credit**: `Equity:Owner's Capital` **$50,000** (Equity increases)
|
41
|
+
|
42
|
+
2. **Buy property for $35,000**:
|
43
|
+
- **Debit**: `Assets:Property` **$35,000** (Property increases)
|
44
|
+
- **Credit**: `Assets:Cash` **$35,000** (Cash decreases)
|
45
|
+
|
46
|
+
3. **Pay $1,500 for property maintenance**:
|
47
|
+
- **Debit**: `Expenses:Maintenance` **$1,500** (Expenses increase)
|
48
|
+
- **Credit**: `Assets:Cash` **$1,500** (Cash decreases)
|
49
|
+
|
50
|
+
4. **Receive $2,000 rental income**:
|
51
|
+
- **Debit**: `Assets:Cash` **$2,000** (Cash increases)
|
52
|
+
- **Credit**: `Income:Rental Income` **$2,000** (Revenue increases)
|
53
|
+
|
54
|
+
---
|
55
|
+
|
56
|
+
### Why Use Debits and Credits?
|
57
|
+
|
58
|
+
Debits and credits enforce the core accounting equation:
|
59
|
+
|
60
|
+
\[
|
61
|
+
\text{Assets} = \text{Liabilities} + \text{Equity}
|
62
|
+
\]
|
63
|
+
|
64
|
+
This ensures financial statements remain balanced, accurate, and easy to verify.
|
65
|
+
|
66
|
+
---
|
67
|
+
|
68
|
+
### Quick Summary:
|
69
|
+
|
70
|
+
- **Debit (Dr.) = Left side**: Increases Assets, Expenses; Decreases Liabilities, Equity, Income
|
71
|
+
- **Credit (Cr.) = Right side**: Increases Liabilities, Equity, Income; Decreases Assets, Expenses
|
72
|
+
|
73
|
+
Each accounting entry must balance with equal **debits and credits**.
|
74
|
+
|
75
|
+
## Ledger
|
76
|
+
|
77
|
+
```
|
78
|
+
$ ledger -f current.ledger bal ^Income ^Expenses --invert
|
79
|
+
-13,166.68 USD Expenses:Maintenance
|
80
|
+
$ ledger -f current.ledger bal Assets Liabilities Equity
|
81
|
+
1,833.32 USD Assets:Mercury Checking ••1090
|
82
|
+
-15,000.00 USD Equity:Capital
|
83
|
+
--------------------
|
84
|
+
-13,166.68 USD
|
85
|
+
```
|
86
|
+
|
87
|
+
### Consistent Currency Formatting
|
88
|
+
|
89
|
+
To ensure all amounts are displayed with the same currency format (using $ symbol), use the `--exchange $` option:
|
90
|
+
|
91
|
+
```
|
92
|
+
$ ledger -f current.ledger --exchange $ bal Assets Liabilities Equity
|
93
|
+
$1,833.32 Assets:Mercury Checking ••1090
|
94
|
+
$-15,000.00 Equity:Capital
|
95
|
+
--------------------
|
96
|
+
$-13,166.68
|
97
|
+
```
|
98
|
+
|
99
|
+
This option converts all currencies to dollars and displays them with the $ symbol, preventing mixed currency formats in the output.
|
100
|
+
|
101
|
+
## Installation
|
102
|
+
|
103
|
+
Add this line to your application's Gemfile:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
gem 'teri'
|
107
|
+
```
|
108
|
+
|
109
|
+
And then execute:
|
110
|
+
|
111
|
+
```bash
|
112
|
+
$ bundle install
|
113
|
+
```
|
114
|
+
|
115
|
+
Or install it yourself as:
|
116
|
+
|
117
|
+
```bash
|
118
|
+
$ gem install teri
|
119
|
+
```
|
120
|
+
|
121
|
+
## Usage
|
122
|
+
|
123
|
+
Teri provides several commands to help you manage your banking transactions:
|
124
|
+
|
125
|
+
### Code Transactions
|
126
|
+
|
127
|
+
```bash
|
128
|
+
# Code new transactions
|
129
|
+
teri code
|
130
|
+
|
131
|
+
# Code transactions using a reconciliation file
|
132
|
+
teri code -f reconcile_example.txt
|
133
|
+
|
134
|
+
# Code transactions using saved responses
|
135
|
+
teri code -r responses.txt
|
136
|
+
|
137
|
+
# Code transactions and save responses
|
138
|
+
teri code -s new_responses.txt
|
139
|
+
|
140
|
+
# Code transactions with OpenAI suggestions
|
141
|
+
# (requires OPENAI_API_KEY environment variable or -k option)
|
142
|
+
teri code
|
143
|
+
|
144
|
+
# Code transactions with a specific OpenAI API key
|
145
|
+
teri code -k your_api_key_here
|
146
|
+
|
147
|
+
# Code transactions without OpenAI suggestions
|
148
|
+
teri code -d
|
149
|
+
|
150
|
+
# Code transactions with auto-apply for OpenAI suggestions
|
151
|
+
teri code -a
|
152
|
+
```
|
153
|
+
|
154
|
+
### AI-Assisted Transaction Coding
|
155
|
+
|
156
|
+
Teri can use OpenAI's API to suggest categories for transactions based on their details and previous codings. This feature helps streamline the coding process by providing intelligent suggestions.
|
157
|
+
|
158
|
+
#### How It Works
|
159
|
+
|
160
|
+
1. When coding a transaction, Teri sends the transaction details (description, amount, counterparty, etc.) to OpenAI's API.
|
161
|
+
2. OpenAI analyzes the transaction and suggests the most appropriate category based on the available categories and previous codings.
|
162
|
+
3. The suggestion is displayed to the user, who can choose to accept it or select a different category.
|
163
|
+
4. The user can also select option 'A' to automatically apply OpenAI's suggestions for all remaining transactions in the session.
|
164
|
+
|
165
|
+
#### Requirements
|
166
|
+
|
167
|
+
- An OpenAI API key (set as the `OPENAI_API_KEY` environment variable or provided with the `-k` option)
|
168
|
+
- The `ruby-openai` gem (included as a dependency)
|
169
|
+
|
170
|
+
#### Configuration Options
|
171
|
+
|
172
|
+
- `-k, --openai-api-key`: Specify the OpenAI API key (defaults to the `OPENAI_API_KEY` environment variable)
|
173
|
+
- `-d, --disable-ai`: Disable AI suggestions
|
174
|
+
- `-a, --auto-apply-ai`: Automatically apply AI suggestions for all transactions
|
175
|
+
|
176
|
+
#### Logging
|
177
|
+
|
178
|
+
Teri automatically logs all interactions with OpenAI and user inputs during coding sessions. This helps with:
|
179
|
+
|
180
|
+
- Debugging issues with AI suggestions
|
181
|
+
- Tracking the prompts sent to OpenAI
|
182
|
+
- Reviewing user decisions and feedback
|
183
|
+
- Auditing transaction categorization decisions
|
184
|
+
|
185
|
+
Log files are stored in the `logs` directory with timestamps in their filenames:
|
186
|
+
- `logs/coding_session_YYYYMMDD_HHMMSS.log`: Contains logs of the coding session, including user interactions and decisions
|
187
|
+
- `logs/openai_YYYYMMDD_HHMMSS.log`: Contains the detailed prompts sent to OpenAI and the responses received
|
188
|
+
|
189
|
+
The logs include:
|
190
|
+
- Transaction details
|
191
|
+
- AI suggestions and confidence levels
|
192
|
+
- User selections and feedback
|
193
|
+
- Categorization decisions
|
194
|
+
|
195
|
+
This logging system helps maintain a record of all transaction coding decisions and the AI's role in those decisions, which can be valuable for auditing and improving the system over time.
|
196
|
+
|
197
|
+
### Check Uncoded Transactions
|
198
|
+
|
199
|
+
```bash
|
200
|
+
# Check for uncoded transactions
|
201
|
+
teri check-uncoded
|
202
|
+
```
|
203
|
+
|
204
|
+
### Generate Reports
|
205
|
+
|
206
|
+
```bash
|
207
|
+
# Generate balance sheet for the last 2 years
|
208
|
+
teri balance-sheet
|
209
|
+
|
210
|
+
# Generate balance sheet for the last 5 years
|
211
|
+
teri balance-sheet -p 5
|
212
|
+
|
213
|
+
# Generate income statement for the last 2 years
|
214
|
+
teri income-statement
|
215
|
+
|
216
|
+
# Generate income statement for the last 5 years
|
217
|
+
teri income-statement -p 5
|
218
|
+
```
|
219
|
+
|
220
|
+
### Close Year
|
221
|
+
|
222
|
+
```bash
|
223
|
+
# Close the books for the previous year
|
224
|
+
teri close-year
|
225
|
+
|
226
|
+
# Close the books for a specific year
|
227
|
+
teri close-year -y 2021
|
228
|
+
```
|
229
|
+
|
230
|
+
### Fix Balance Sheet
|
231
|
+
|
232
|
+
```bash
|
233
|
+
# Fix the balance sheet
|
234
|
+
teri fix-balance
|
235
|
+
```
|
236
|
+
|
237
|
+
### Version Information
|
238
|
+
|
239
|
+
```bash
|
240
|
+
# Display version information
|
241
|
+
teri version
|
242
|
+
```
|
243
|
+
|
244
|
+
## File Structure
|
245
|
+
|
246
|
+
- `transactions/*.ledger`: Original transaction files
|
247
|
+
- `coding.ledger`: Contains reverse transactions for proper categorization
|
248
|
+
|
249
|
+
## Reconciliation File Format
|
250
|
+
|
251
|
+
You can use a reconciliation file to batch process transactions instead of coding them interactively. The file format is:
|
252
|
+
|
253
|
+
```
|
254
|
+
transaction_id,category1:amount1,category2:amount2,...
|
255
|
+
```
|
256
|
+
|
257
|
+
- If amount is not specified, the full transaction amount will be used
|
258
|
+
- For split transactions, specify multiple categories with their respective amounts
|
259
|
+
- Lines starting with # are treated as comments
|
260
|
+
|
261
|
+
Example:
|
262
|
+
```
|
263
|
+
# Single category coding
|
264
|
+
0ac547b2-8d8e-11eb-870c-ef6812d46c47,Expenses:Rent
|
265
|
+
|
266
|
+
# Split transaction
|
267
|
+
dbd348b4-8d88-11eb-8f51-5f5908fef419,Income:Sales:10000.00,Income:Services:5000.00
|
268
|
+
|
269
|
+
# Loan payment split between principal and interest
|
270
|
+
46fa4d2e-be4c-11eb-a705-afde9e1ac01b,Expenses:Loan:Principal:150.00,Expenses:Interest:28.00
|
271
|
+
```
|
272
|
+
|
273
|
+
## Development
|
274
|
+
|
275
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
276
|
+
|
277
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
278
|
+
|
279
|
+
## Contributing
|
280
|
+
|
281
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/example/teri.
|
282
|
+
|
283
|
+
## License
|
284
|
+
|
285
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "teri"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/bin/teri
ADDED
@@ -0,0 +1,310 @@
|
|
1
|
+
require 'English'
|
2
|
+
require 'date'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'securerandom'
|
5
|
+
require 'logger'
|
6
|
+
require_relative 'openai_client'
|
7
|
+
require_relative 'category_manager'
|
8
|
+
require_relative 'transaction_coder'
|
9
|
+
require_relative 'report_generator'
|
10
|
+
require_relative 'ai_integration'
|
11
|
+
|
12
|
+
module Teri
|
13
|
+
# IO adapter for handling input/output operations
|
14
|
+
# This allows for better testing by injecting a test adapter
|
15
|
+
class IOAdapter
|
16
|
+
def puts(message)
|
17
|
+
Kernel.puts message
|
18
|
+
end
|
19
|
+
|
20
|
+
def print(message)
|
21
|
+
Kernel.print message
|
22
|
+
end
|
23
|
+
|
24
|
+
def gets
|
25
|
+
$stdin.gets
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# File adapter for handling file operations
|
30
|
+
# This allows for better testing by injecting a test adapter
|
31
|
+
class FileAdapter
|
32
|
+
def exist?(path)
|
33
|
+
File.exist?(path)
|
34
|
+
end
|
35
|
+
|
36
|
+
def read(path)
|
37
|
+
File.read(path)
|
38
|
+
end
|
39
|
+
|
40
|
+
def readlines(path)
|
41
|
+
File.readlines(path)
|
42
|
+
end
|
43
|
+
|
44
|
+
def open(path, mode, &)
|
45
|
+
File.open(path, mode, &)
|
46
|
+
end
|
47
|
+
|
48
|
+
def warning(message)
|
49
|
+
puts "Warning: #{message}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Accounting
|
54
|
+
attr_reader :transactions, :coded_transactions, :options, :logger, :previous_codings, :counterparty_hints
|
55
|
+
|
56
|
+
def initialize(options = {})
|
57
|
+
@transactions = []
|
58
|
+
@coded_transactions = {}
|
59
|
+
|
60
|
+
# Convert string keys to symbol keys
|
61
|
+
symbolized_options = {}
|
62
|
+
options.each do |key, value|
|
63
|
+
symbolized_options[key.to_sym] = value
|
64
|
+
end
|
65
|
+
|
66
|
+
@options = {
|
67
|
+
year: Date.today.year,
|
68
|
+
month: nil,
|
69
|
+
periods: 2, # Default to 2 previous periods
|
70
|
+
response_file: nil, # Add option for response file
|
71
|
+
save_responses_file: nil, # Add option for saving responses
|
72
|
+
adjustment_asset_account: nil, # Add option for adjustment asset account
|
73
|
+
adjustment_equity_account: nil, # Add option for adjustment equity account
|
74
|
+
openai_api_key: ENV.fetch('OPENAI_API_KEY', nil), # Add option for OpenAI API key
|
75
|
+
use_ai_suggestions: true, # Add option to enable/disable AI suggestions
|
76
|
+
auto_apply_ai: false, # Add option to auto-apply AI suggestions
|
77
|
+
log_file: nil,
|
78
|
+
}.merge(symbolized_options)
|
79
|
+
|
80
|
+
# Set up logging
|
81
|
+
setup_logger
|
82
|
+
|
83
|
+
# Initialize IO and File adapters
|
84
|
+
@io = IOAdapter.new
|
85
|
+
@file = FileAdapter.new
|
86
|
+
|
87
|
+
# Initialize category manager
|
88
|
+
@category_manager = CategoryManager.new
|
89
|
+
|
90
|
+
# Initialize AI integration
|
91
|
+
@ai_integration = AIIntegration.new(@options, @logger, @log_file)
|
92
|
+
@openai_client = @ai_integration.openai_client
|
93
|
+
|
94
|
+
# Initialize transaction coder
|
95
|
+
@transaction_coder = TransactionCoder.new(@category_manager, @ai_integration, @options, @logger)
|
96
|
+
|
97
|
+
# Initialize report generator
|
98
|
+
@report_generator = ReportGenerator.new(@options, @logger)
|
99
|
+
|
100
|
+
# Initialize previous codings cache
|
101
|
+
@previous_codings = {}
|
102
|
+
@counterparty_hints = {}
|
103
|
+
|
104
|
+
# Load previous codings if we have an OpenAI client and a file
|
105
|
+
load_previous_codings(@file) if @openai_client && @file
|
106
|
+
end
|
107
|
+
|
108
|
+
# Set up the logger
|
109
|
+
def setup_logger
|
110
|
+
# Skip logger setup if we're in a test environment
|
111
|
+
if defined?(RSpec)
|
112
|
+
@logger = nil
|
113
|
+
return
|
114
|
+
end
|
115
|
+
|
116
|
+
begin
|
117
|
+
# Create logs directory if it doesn't exist
|
118
|
+
FileUtils.mkdir_p('logs')
|
119
|
+
|
120
|
+
# Create a log file with timestamp
|
121
|
+
@log_file = "logs/coding_session_#{Time.now.strftime('%Y%m%d_%H%M%S')}.log"
|
122
|
+
|
123
|
+
@logger = Logger.new(@log_file)
|
124
|
+
@logger.level = Logger::INFO
|
125
|
+
@logger.formatter = proc do |severity, datetime, _progname, msg|
|
126
|
+
"#{datetime.strftime('%Y-%m-%d %H:%M:%S')} [#{severity}] #{msg}\n"
|
127
|
+
end
|
128
|
+
|
129
|
+
@logger.info('Coding session started')
|
130
|
+
@logger.info("Options: #{@options.inspect}")
|
131
|
+
rescue StandardError => e
|
132
|
+
puts "Warning: Failed to set up logging: #{e.message}"
|
133
|
+
@logger = nil
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def load_transactions(filelist: Dir.glob('transactions/*.ledger'))
|
138
|
+
# Clear existing transactions
|
139
|
+
@transactions = []
|
140
|
+
|
141
|
+
# Load all transactions from ledger files
|
142
|
+
filelist.each do |file|
|
143
|
+
ledger = Ledger.parse(file)
|
144
|
+
file_transactions = ledger.transactions.map { |hash| Transaction.from_ledger_hash(hash) }
|
145
|
+
@transactions.concat(file_transactions)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def load_coded_transactions
|
150
|
+
# Initialize the coded transactions hash
|
151
|
+
@coded_transactions = {}
|
152
|
+
|
153
|
+
# Check if coding.ledger exists
|
154
|
+
return unless File.exist?('coding.ledger')
|
155
|
+
|
156
|
+
# Use Ledger.parse to read the coding.ledger file
|
157
|
+
Ledger.parse('coding.ledger').transactions.each do |transaction|
|
158
|
+
@coded_transactions[transaction[:transaction_id]] = true
|
159
|
+
end
|
160
|
+
|
161
|
+
# Also check for transaction IDs directly in the file content
|
162
|
+
# This is a fallback in case the Ledger.parse method doesn't capture all transaction IDs
|
163
|
+
begin
|
164
|
+
coding_ledger_content = File.read('coding.ledger')
|
165
|
+
# Look for transaction IDs in comments (e.g., "; Original Transaction ID: 1234-5678")
|
166
|
+
coding_ledger_content.scan(/;\s*(?:Original\s+)?Transaction\s+ID:?\s*([a-zA-Z0-9-]+)/).each do |match|
|
167
|
+
@coded_transactions[match[0]] = true
|
168
|
+
end
|
169
|
+
|
170
|
+
# Also look for transaction IDs in the transaction headers
|
171
|
+
# Format: YYYY-MM-DD * Transaction Description ; Transaction ID: 1234-5678
|
172
|
+
coding_ledger_content.scan(/\d{4}-\d{2}-\d{2}.*?;\s*(?:Transaction\s+ID:?\s*|ID:?\s*)([a-zA-Z0-9-]+)/).each do |match|
|
173
|
+
@coded_transactions[match[0]] = true
|
174
|
+
end
|
175
|
+
rescue StandardError => e
|
176
|
+
@logger&.error("Error reading coding.ledger file: #{e.message}")
|
177
|
+
puts "Warning: Error reading coding.ledger file: #{e.message}"
|
178
|
+
end
|
179
|
+
|
180
|
+
@logger&.info("Loaded #{@coded_transactions.size} coded transactions")
|
181
|
+
@coded_transactions
|
182
|
+
end
|
183
|
+
|
184
|
+
def code_transactions
|
185
|
+
load_transactions
|
186
|
+
|
187
|
+
# Get uncoded transactions
|
188
|
+
uncoded_transactions = @transactions.select do |transaction|
|
189
|
+
transaction.entries.any? { |entry| entry.account.include?('Unknown') }
|
190
|
+
end
|
191
|
+
|
192
|
+
if uncoded_transactions.empty?
|
193
|
+
puts 'No uncoded transactions found.'
|
194
|
+
return
|
195
|
+
end
|
196
|
+
|
197
|
+
# Check if we should use a reconciliation file
|
198
|
+
return process_reconcile_file(uncoded_transactions) if @options[:reconcile_file]
|
199
|
+
|
200
|
+
# Initialize variables for responses
|
201
|
+
responses = nil
|
202
|
+
saved_responses = nil
|
203
|
+
|
204
|
+
# Check if we should use saved responses
|
205
|
+
if @options[:responses_file]
|
206
|
+
if File.exist?(@options[:responses_file])
|
207
|
+
@logger&.info("Using responses from file: #{@options[:responses_file]}")
|
208
|
+
puts "Using responses from file: #{@options[:responses_file]}"
|
209
|
+
responses = File.readlines(@options[:responses_file]).map(&:strip)
|
210
|
+
else
|
211
|
+
@logger&.error("Responses file not found: #{@options[:responses_file]}")
|
212
|
+
puts "Error: Responses file not found: #{@options[:responses_file]}"
|
213
|
+
return
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Check if we should save responses
|
218
|
+
if @options[:save_responses_file]
|
219
|
+
@logger&.info("Saving responses to file: #{@options[:save_responses_file]}")
|
220
|
+
puts "Saving responses to file: #{@options[:save_responses_file]}"
|
221
|
+
saved_responses = []
|
222
|
+
end
|
223
|
+
|
224
|
+
# Load previous codings for AI suggestions
|
225
|
+
load_previous_codings(@file) if @openai_client
|
226
|
+
|
227
|
+
# Code each transaction
|
228
|
+
uncoded_transactions.each do |transaction|
|
229
|
+
@logger&.info("Coding transaction: #{transaction.transaction_id} - #{transaction.description}")
|
230
|
+
|
231
|
+
# Code the transaction interactively
|
232
|
+
result = @transaction_coder.code_transaction_interactively(
|
233
|
+
transaction,
|
234
|
+
responses,
|
235
|
+
saved_responses,
|
236
|
+
@options[:auto_apply_ai],
|
237
|
+
@io
|
238
|
+
)
|
239
|
+
|
240
|
+
# Check if the user wants to auto-apply AI suggestions
|
241
|
+
if result == 'A'
|
242
|
+
@logger&.info('User selected auto-apply AI suggestions')
|
243
|
+
@options[:auto_apply_ai] = true
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# Save responses if requested
|
248
|
+
return unless @options[:save_responses_file] && saved_responses
|
249
|
+
|
250
|
+
@logger&.info("Writing #{saved_responses.size} responses to file: #{@options[:save_responses_file]}")
|
251
|
+
File.write(@options[:save_responses_file], saved_responses.join("\n"))
|
252
|
+
puts "Responses saved to: #{@options[:save_responses_file]}"
|
253
|
+
end
|
254
|
+
|
255
|
+
def process_reconcile_file(uncoded_transactions)
|
256
|
+
@transaction_coder.process_reconcile_file(uncoded_transactions, @options[:reconcile_file])
|
257
|
+
end
|
258
|
+
|
259
|
+
def check_uncoded_transactions
|
260
|
+
# Load transactions from ledger files
|
261
|
+
load_transactions
|
262
|
+
|
263
|
+
# Load previously coded transactions
|
264
|
+
load_coded_transactions
|
265
|
+
|
266
|
+
# Find uncoded transactions
|
267
|
+
@transactions.reject { |t| @coded_transactions[t.transaction_id] }
|
268
|
+
end
|
269
|
+
|
270
|
+
def generate_balance_sheet(options = {})
|
271
|
+
@report_generator.generate_balance_sheet(options)
|
272
|
+
end
|
273
|
+
|
274
|
+
def generate_income_statement(options = {})
|
275
|
+
@report_generator.generate_income_statement(options)
|
276
|
+
end
|
277
|
+
|
278
|
+
def all_categories
|
279
|
+
@category_manager.all_categories
|
280
|
+
end
|
281
|
+
|
282
|
+
# Load previous codings from coding.ledger for AI suggestions
|
283
|
+
def load_previous_codings(file_adapter = @file)
|
284
|
+
# Delegate to AI integration
|
285
|
+
@ai_integration.load_previous_codings(file_adapter)
|
286
|
+
|
287
|
+
# Update our local references to the data
|
288
|
+
@previous_codings = @ai_integration.previous_codings
|
289
|
+
@counterparty_hints = @ai_integration.counterparty_hints
|
290
|
+
end
|
291
|
+
|
292
|
+
# Expose previous codings for OpenAI client
|
293
|
+
def previous_codings
|
294
|
+
@previous_codings
|
295
|
+
end
|
296
|
+
|
297
|
+
# Expose counterparty hints for OpenAI client
|
298
|
+
def counterparty_hints
|
299
|
+
@counterparty_hints
|
300
|
+
end
|
301
|
+
|
302
|
+
def code_transaction(transaction, selected_option, split_input = nil, new_category = nil)
|
303
|
+
@transaction_coder.code_transaction(transaction, selected_option, split_input, new_category)
|
304
|
+
end
|
305
|
+
|
306
|
+
def code_transaction_interactively(transaction, responses = nil, saved_responses = nil, auto_apply_ai = false, io = @io)
|
307
|
+
@transaction_coder.code_transaction_interactively(transaction, responses, saved_responses, auto_apply_ai, io)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|