Class: Teri::Accounting
- Inherits:
-
Object
- Object
- Teri::Accounting
- Defined in:
- lib/teri/accounting.rb
Instance Attribute Summary collapse
-
#coded_transactions ⇒ Object
readonly
Returns the value of attribute coded_transactions.
-
#counterparty_hints ⇒ Object
readonly
Expose counterparty hints for OpenAI client.
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
-
#previous_codings ⇒ Object
readonly
Expose previous codings for OpenAI client.
-
#transactions ⇒ Object
readonly
Returns the value of attribute transactions.
Instance Method Summary collapse
- #all_categories ⇒ Object
- #check_uncoded_transactions ⇒ Object
- #code_transaction(transaction, selected_option, split_input = nil, new_category = nil) ⇒ Object
- #code_transaction_interactively(transaction, responses = nil, saved_responses = nil, auto_apply_ai = false, io = @io) ⇒ Object
- #code_transactions ⇒ Object
- #generate_balance_sheet(options = {}) ⇒ Object
- #generate_income_statement(options = {}) ⇒ Object
-
#initialize(options = {}) ⇒ Accounting
constructor
A new instance of Accounting.
- #load_coded_transactions ⇒ Object
-
#load_previous_codings(file_adapter = @file) ⇒ Object
Load previous codings from coding.ledger for AI suggestions.
- #load_transactions(filelist: Dir.glob('transactions/*.ledger')) ⇒ Object
- #process_reconcile_file(uncoded_transactions) ⇒ Object
-
#setup_logger ⇒ Object
Set up the logger.
Constructor Details
#initialize(options = {}) ⇒ Accounting
Returns a new instance of Accounting.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/teri/accounting.rb', line 56 def initialize( = {}) @transactions = [] @coded_transactions = {} # Convert string keys to symbol keys = {} .each do |key, value| [key.to_sym] = value end @options = { year: Date.today.year, month: nil, periods: 2, # Default to 2 previous periods response_file: nil, # Add option for response file save_responses_file: nil, # Add option for saving responses adjustment_asset_account: nil, # Add option for adjustment asset account adjustment_equity_account: nil, # Add option for adjustment equity account openai_api_key: ENV.fetch('OPENAI_API_KEY', nil), # Add option for OpenAI API key use_ai_suggestions: true, # Add option to enable/disable AI suggestions auto_apply_ai: false, # Add option to auto-apply AI suggestions log_file: nil, }.merge() # Set up logging setup_logger # Initialize IO and File adapters @io = IOAdapter.new @file = FileAdapter.new # Initialize category manager @category_manager = CategoryManager.new # Initialize AI integration @ai_integration = AIIntegration.new(@options, @logger, @log_file) @openai_client = @ai_integration.openai_client # Initialize transaction coder @transaction_coder = TransactionCoder.new(@category_manager, @ai_integration, @options, @logger) # Initialize report generator @report_generator = ReportGenerator.new(@options, @logger) # Initialize previous codings cache @previous_codings = {} @counterparty_hints = {} # Load previous codings if we have an OpenAI client and a file load_previous_codings(@file) if @openai_client && @file end |
Instance Attribute Details
#coded_transactions ⇒ Object (readonly)
Returns the value of attribute coded_transactions.
54 55 56 |
# File 'lib/teri/accounting.rb', line 54 def coded_transactions @coded_transactions end |
#counterparty_hints ⇒ Object (readonly)
Expose counterparty hints for OpenAI client
298 299 300 |
# File 'lib/teri/accounting.rb', line 298 def counterparty_hints @counterparty_hints end |
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
54 55 56 |
# File 'lib/teri/accounting.rb', line 54 def logger @logger end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
54 55 56 |
# File 'lib/teri/accounting.rb', line 54 def @options end |
#previous_codings ⇒ Object (readonly)
Expose previous codings for OpenAI client
293 294 295 |
# File 'lib/teri/accounting.rb', line 293 def previous_codings @previous_codings end |
#transactions ⇒ Object (readonly)
Returns the value of attribute transactions.
54 55 56 |
# File 'lib/teri/accounting.rb', line 54 def transactions @transactions end |
Instance Method Details
#all_categories ⇒ Object
278 279 280 |
# File 'lib/teri/accounting.rb', line 278 def all_categories @category_manager.all_categories end |
#check_uncoded_transactions ⇒ Object
259 260 261 262 263 264 265 266 267 268 |
# File 'lib/teri/accounting.rb', line 259 def check_uncoded_transactions # Load transactions from ledger files load_transactions # Load previously coded transactions load_coded_transactions # Find uncoded transactions @transactions.reject { |t| @coded_transactions[t.transaction_id] } end |
#code_transaction(transaction, selected_option, split_input = nil, new_category = nil) ⇒ Object
302 303 304 |
# File 'lib/teri/accounting.rb', line 302 def code_transaction(transaction, selected_option, split_input = nil, new_category = nil) @transaction_coder.code_transaction(transaction, selected_option, split_input, new_category) end |
#code_transaction_interactively(transaction, responses = nil, saved_responses = nil, auto_apply_ai = false, io = @io) ⇒ Object
306 307 308 |
# File 'lib/teri/accounting.rb', line 306 def code_transaction_interactively(transaction, responses = nil, saved_responses = nil, auto_apply_ai = false, io = @io) @transaction_coder.code_transaction_interactively(transaction, responses, saved_responses, auto_apply_ai, io) end |
#code_transactions ⇒ Object
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/teri/accounting.rb', line 184 def code_transactions load_transactions # Get uncoded transactions uncoded_transactions = @transactions.select do |transaction| transaction.entries.any? { |entry| entry.account.include?('Unknown') } end if uncoded_transactions.empty? puts 'No uncoded transactions found.' return end # Check if we should use a reconciliation file return process_reconcile_file(uncoded_transactions) if @options[:reconcile_file] # Initialize variables for responses responses = nil saved_responses = nil # Check if we should use saved responses if @options[:responses_file] if File.exist?(@options[:responses_file]) @logger&.info("Using responses from file: #{@options[:responses_file]}") puts "Using responses from file: #{@options[:responses_file]}" responses = File.readlines(@options[:responses_file]).map(&:strip) else @logger&.error("Responses file not found: #{@options[:responses_file]}") puts "Error: Responses file not found: #{@options[:responses_file]}" return end end # Check if we should save responses if @options[:save_responses_file] @logger&.info("Saving responses to file: #{@options[:save_responses_file]}") puts "Saving responses to file: #{@options[:save_responses_file]}" saved_responses = [] end # Load previous codings for AI suggestions load_previous_codings(@file) if @openai_client # Code each transaction uncoded_transactions.each do |transaction| @logger&.info("Coding transaction: #{transaction.transaction_id} - #{transaction.description}") # Code the transaction interactively result = @transaction_coder.code_transaction_interactively( transaction, responses, saved_responses, @options[:auto_apply_ai], @io ) # Check if the user wants to auto-apply AI suggestions if result == 'A' @logger&.info('User selected auto-apply AI suggestions') @options[:auto_apply_ai] = true end end # Save responses if requested return unless @options[:save_responses_file] && saved_responses @logger&.info("Writing #{saved_responses.size} responses to file: #{@options[:save_responses_file]}") File.write(@options[:save_responses_file], saved_responses.join("\n")) puts "Responses saved to: #{@options[:save_responses_file]}" end |
#generate_balance_sheet(options = {}) ⇒ Object
270 271 272 |
# File 'lib/teri/accounting.rb', line 270 def generate_balance_sheet( = {}) @report_generator.generate_balance_sheet() end |
#generate_income_statement(options = {}) ⇒ Object
274 275 276 |
# File 'lib/teri/accounting.rb', line 274 def generate_income_statement( = {}) @report_generator.generate_income_statement() end |
#load_coded_transactions ⇒ Object
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/teri/accounting.rb', line 149 def load_coded_transactions # Initialize the coded transactions hash @coded_transactions = {} # Check if coding.ledger exists return unless File.exist?('coding.ledger') # Use Ledger.parse to read the coding.ledger file Ledger.parse('coding.ledger').transactions.each do |transaction| @coded_transactions[transaction[:transaction_id]] = true end # Also check for transaction IDs directly in the file content # This is a fallback in case the Ledger.parse method doesn't capture all transaction IDs begin coding_ledger_content = File.read('coding.ledger') # Look for transaction IDs in comments (e.g., "; Original Transaction ID: 1234-5678") coding_ledger_content.scan(/;\s*(?:Original\s+)?Transaction\s+ID:?\s*([a-zA-Z0-9-]+)/).each do |match| @coded_transactions[match[0]] = true end # Also look for transaction IDs in the transaction headers # Format: YYYY-MM-DD * Transaction Description ; Transaction ID: 1234-5678 coding_ledger_content.scan(/\d{4}-\d{2}-\d{2}.*?;\s*(?:Transaction\s+ID:?\s*|ID:?\s*)([a-zA-Z0-9-]+)/).each do |match| @coded_transactions[match[0]] = true end rescue StandardError => e @logger&.error("Error reading coding.ledger file: #{e.}") puts "Warning: Error reading coding.ledger file: #{e.}" end @logger&.info("Loaded #{@coded_transactions.size} coded transactions") @coded_transactions end |
#load_previous_codings(file_adapter = @file) ⇒ Object
Load previous codings from coding.ledger for AI suggestions
283 284 285 286 287 288 289 290 |
# File 'lib/teri/accounting.rb', line 283 def load_previous_codings(file_adapter = @file) # Delegate to AI integration @ai_integration.load_previous_codings(file_adapter) # Update our local references to the data @previous_codings = @ai_integration.previous_codings @counterparty_hints = @ai_integration.counterparty_hints end |
#load_transactions(filelist: Dir.glob('transactions/*.ledger')) ⇒ Object
137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/teri/accounting.rb', line 137 def load_transactions(filelist: Dir.glob('transactions/*.ledger')) # Clear existing transactions @transactions = [] # Load all transactions from ledger files filelist.each do |file| ledger = Ledger.parse(file) file_transactions = ledger.transactions.map { |hash| Transaction.from_ledger_hash(hash) } @transactions.concat(file_transactions) end end |
#process_reconcile_file(uncoded_transactions) ⇒ Object
255 256 257 |
# File 'lib/teri/accounting.rb', line 255 def process_reconcile_file(uncoded_transactions) @transaction_coder.process_reconcile_file(uncoded_transactions, @options[:reconcile_file]) end |
#setup_logger ⇒ Object
Set up the logger
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/teri/accounting.rb', line 109 def setup_logger # Skip logger setup if we're in a test environment if defined?(RSpec) @logger = nil return end begin # Create logs directory if it doesn't exist FileUtils.mkdir_p('logs') # Create a log file with timestamp @log_file = "logs/coding_session_#{Time.now.strftime('%Y%m%d_%H%M%S')}.log" @logger = Logger.new(@log_file) @logger.level = Logger::INFO @logger.formatter = proc do |severity, datetime, _progname, msg| "#{datetime.strftime('%Y-%m-%d %H:%M:%S')} [#{severity}] #{msg}\n" end @logger.info('Coding session started') @logger.info("Options: #{@options.inspect}") rescue StandardError => e puts "Warning: Failed to set up logging: #{e.}" @logger = nil end end |