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 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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/bin/teri ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'teri'
4
+
5
+ Teri::CLI.start(ARGV)
@@ -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