appydave-tools 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +15 -0
- data/CHANGELOG.md +14 -0
- data/bin/bank_reconciliation.rb +99 -0
- data/bin/configuration.rb +12 -4
- data/lib/appydave/tools/bank_reconciliation/_doc.md +36 -0
- data/lib/appydave/tools/bank_reconciliation/clean/clean_transactions.rb +95 -0
- data/lib/appydave/tools/bank_reconciliation/clean/mapper.rb +110 -0
- data/lib/appydave/tools/bank_reconciliation/clean/read_transactions.rb +62 -0
- data/lib/appydave/tools/bank_reconciliation/models/transaction.rb +55 -0
- data/lib/appydave/tools/configuration/config.rb +19 -3
- data/lib/appydave/tools/configuration/models/bank_reconciliation_config.rb +11 -4
- data/lib/appydave/tools/configuration/models/channels_config.rb +20 -1
- data/lib/appydave/tools/version.rb +1 -1
- data/lib/appydave/tools.rb +5 -0
- data/package-lock.json +2 -2
- data/package.json +1 -1
- metadata +22 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 14a6bd2963b58f85f247282a6cf8c537495b9a18300b04c3a3794ef5df4ecaa1
         | 
| 4 | 
            +
              data.tar.gz: 5ec47924ab4268377a297e1f02f6fa6b4f62207179f21fe8ccdcd69d62cd4f8d
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 253546f33096bad9f9daaa5776fa9d98142a4e7cb43607bf2040ab648d49f83712c8f491b8730de4bd602bcd4080a9360cb9bdd082a1b249df4210d66083ebf6
         | 
| 7 | 
            +
              data.tar.gz: f1f638a4a299df238694c8b4e42c008e8663a08cb2b83931a953796ec6679fa83038bebe3f749b0351a7edd848220cb906c53ff076abbf953564ff0356347b29
         | 
    
        data/.rubocop.yml
    CHANGED
    
    | @@ -80,6 +80,7 @@ Style/EmptyMethod: | |
| 80 80 | 
             
            Metrics/ParameterLists:
         | 
| 81 81 | 
             
              Exclude:
         | 
| 82 82 | 
             
                - "**/spec/**/*"
         | 
| 83 | 
            +
                - "**/models/**/*"
         | 
| 83 84 | 
             
            Layout/EmptyLineBetweenDefs:
         | 
| 84 85 | 
             
              Exclude:
         | 
| 85 86 | 
             
                - "**/spec/**/*"
         | 
| @@ -115,3 +116,17 @@ RSpec/DescribeClass: | |
| 115 116 |  | 
| 116 117 | 
             
            RSpec/PendingWithoutReason:
         | 
| 117 118 | 
             
              Enabled: false
         | 
| 119 | 
            +
             | 
| 120 | 
            +
            Metrics/AbcSize:
         | 
| 121 | 
            +
              Max: 25
         | 
| 122 | 
            +
              Exclude:
         | 
| 123 | 
            +
                - "bin/*"
         | 
| 124 | 
            +
            Metrics/CyclomaticComplexity:
         | 
| 125 | 
            +
              Exclude:
         | 
| 126 | 
            +
                - "**/models/**/*"
         | 
| 127 | 
            +
                - "lib/appydave/tools/bank_reconciliation/clean/mapper.rb"
         | 
| 128 | 
            +
            Metrics/PerceivedComplexity:
         | 
| 129 | 
            +
              Exclude:
         | 
| 130 | 
            +
                - "**/models/**/*"
         | 
| 131 | 
            +
            RSpec/MultipleMemoizedHelpers:
         | 
| 132 | 
            +
              Enabled: false
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,3 +1,17 @@ | |
| 1 | 
            +
            ## [0.6.1](https://github.com/klueless-io/appydave-tools/compare/v0.6.0...v0.6.1) (2024-05-26)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
             | 
| 4 | 
            +
            ### Bug Fixes
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            * improved configuration printing ([89d769d](https://github.com/klueless-io/appydave-tools/commit/89d769d0741fc75b44db90931cf981feea83027f))
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            # [0.6.0](https://github.com/klueless-io/appydave-tools/compare/v0.5.0...v0.6.0) (2024-05-26)
         | 
| 9 | 
            +
             | 
| 10 | 
            +
             | 
| 11 | 
            +
            ### Features
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            * refactor channels with locations, removed channel projects ([6b64574](https://github.com/klueless-io/appydave-tools/commit/6b645742b0029a001792c8d405dcae0b1036f2c0))
         | 
| 14 | 
            +
             | 
| 1 15 | 
             
            # [0.5.0](https://github.com/klueless-io/appydave-tools/compare/v0.4.1...v0.5.0) (2024-05-26)
         | 
| 2 16 |  | 
| 3 17 |  | 
| @@ -0,0 +1,99 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            require 'pry'
         | 
| 7 | 
            +
            require 'appydave/tools'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            # !/usr/bin/env ruby
         | 
| 10 | 
            +
            # frozen_string_literal: true
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            # Process command line arguments for any bank reconciliation operations
         | 
| 13 | 
            +
            class BankReconciliationCLI
         | 
| 14 | 
            +
              def initialize
         | 
| 15 | 
            +
                @commands = {
         | 
| 16 | 
            +
                  'clean' => method(:clean_transactions),
         | 
| 17 | 
            +
                  'process' => method(:process_transactions),
         | 
| 18 | 
            +
                  'filter' => method(:filter_transactions)
         | 
| 19 | 
            +
                }
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def run
         | 
| 23 | 
            +
                command, *args = ARGV
         | 
| 24 | 
            +
                if @commands.key?(command)
         | 
| 25 | 
            +
                  @commands[command].call(args)
         | 
| 26 | 
            +
                else
         | 
| 27 | 
            +
                  puts "Unknown command: #{command}"
         | 
| 28 | 
            +
                  print_help
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              private
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              def clean_transactions(args)
         | 
| 35 | 
            +
                options = {}
         | 
| 36 | 
            +
                OptionParser.new do |opts|
         | 
| 37 | 
            +
                  opts.banner = 'Usage: bank_reconciliation.rb clean [options]'
         | 
| 38 | 
            +
                  opts.on('-i', '--include PATTERN', 'GLOB pattern for source transaction files') { |v| options[:include] = v }
         | 
| 39 | 
            +
                  opts.on('-f', '--transaction FOLDER', 'Transaction CSV folder where original banking CSV files are stored') { |v| options[:transaction_folder] = v }
         | 
| 40 | 
            +
                  opts.on('-o', '--output FILE', 'Output CSV file name') { |v| options[:output] = v }
         | 
| 41 | 
            +
                  opts.on_tail('-h', '--help', 'Show this message') do
         | 
| 42 | 
            +
                    puts opts
         | 
| 43 | 
            +
                    exit
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                end.parse!(args)
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                # Implement cleaning and normalizing transactions
         | 
| 48 | 
            +
                puts "Cleaning transactions with options: #{options}"
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
              def process_transactions(args)
         | 
| 52 | 
            +
                options = {}
         | 
| 53 | 
            +
                OptionParser.new do |opts|
         | 
| 54 | 
            +
                  opts.banner = 'Usage: bank_reconciliation.rb process [options]'
         | 
| 55 | 
            +
                  opts.on('-i', '--input FILE', 'Input CSV file with transactions') { |v| options[:input] = v }
         | 
| 56 | 
            +
                  opts.on_tail('-h', '--help', 'Show this message') do
         | 
| 57 | 
            +
                    puts opts
         | 
| 58 | 
            +
                    exit
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
                end.parse!(args)
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                # Implement processing transactions with chart of accounts lookup
         | 
| 63 | 
            +
                puts "Processing transactions with options: #{options}"
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
              def filter_transactions(args)
         | 
| 67 | 
            +
                options = {}
         | 
| 68 | 
            +
                OptionParser.new do |opts|
         | 
| 69 | 
            +
                  opts.banner = 'Usage: bank_reconciliation.rb filter [options]'
         | 
| 70 | 
            +
                  opts.on('-i', '--input FILE', 'Input CSV file with processed transactions') { |v| options[:input] = v }
         | 
| 71 | 
            +
                  opts.on('-y', '--year YEAR', 'Filter by financial year') { |v| options[:year] = v }
         | 
| 72 | 
            +
                  opts.on('-b', '--begin DATE', 'Filter by dates greater than or eqaul to DDMMYY') { |v| options[:year] = v }
         | 
| 73 | 
            +
                  opts.on('-e', '--end DATE', 'Filter by dates less than or eqaul to DDMMYY') { |v| options[:year] = v }
         | 
| 74 | 
            +
                  opts.on('-c', '--codes CODES', 'Filter by chart of account codes (comma-separated)') { |v| options[:codes] = v }
         | 
| 75 | 
            +
                  opts.on('-w', '--wild TEXT', 'Wildcard text match') { |v| options[:text] = v }
         | 
| 76 | 
            +
                  opts.on('-d', '--display', 'Display filtered transactions in table format') { |v| options[:display] = v }
         | 
| 77 | 
            +
                  opts.on('-o', '--output FILE', 'Output CSV file name') { |v| options[:output] = v }
         | 
| 78 | 
            +
                  opts.on_tail('-h', '--help', 'Show this message') do
         | 
| 79 | 
            +
                    puts opts
         | 
| 80 | 
            +
                    exit
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
                end.parse!(args)
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                # Implement filtering of processed transactions
         | 
| 85 | 
            +
                puts "Filtering transactions with options: #{options}"
         | 
| 86 | 
            +
              end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
              def print_help
         | 
| 89 | 
            +
                puts 'Usage: bank_reconciliation.rb [command] [options]'
         | 
| 90 | 
            +
                puts 'Commands:'
         | 
| 91 | 
            +
                puts '  clean    Clean and normalize transaction files'
         | 
| 92 | 
            +
                puts '  process  Process transaction list via chart of accounts lookup'
         | 
| 93 | 
            +
                puts '  filter   Filter processed transaction list'
         | 
| 94 | 
            +
                puts "Run 'bank_reconciliation.rb [command] --help' for more information on a command."
         | 
| 95 | 
            +
              end
         | 
| 96 | 
            +
            end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
            BankReconciliationCLI.new.run
         | 
| 99 | 
            +
            # BankReconciliationCLI.new.run if __FILE__ == $PROGRAM_NAME
         | 
    
        data/bin/configuration.rb
    CHANGED
    
    | @@ -1,12 +1,19 @@ | |
| 1 1 | 
             
            #!/usr/bin/env ruby
         | 
| 2 2 | 
             
            # frozen_string_literal: true
         | 
| 3 3 |  | 
| 4 | 
            +
            # Example of a simple command line tool to manage configuration files
         | 
| 5 | 
            +
            # ad_config -p settings,channels
         | 
| 6 | 
            +
            # ad_config -p
         | 
| 7 | 
            +
            # ad_config -l
         | 
| 8 | 
            +
            # ad_config -c
         | 
| 9 | 
            +
            # ad_config -e
         | 
| 10 | 
            +
             | 
| 4 11 | 
             
            $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
         | 
| 5 12 |  | 
| 6 13 | 
             
            require 'pry'
         | 
| 7 14 | 
             
            require 'appydave/tools'
         | 
| 8 15 |  | 
| 9 | 
            -
            options = {}
         | 
| 16 | 
            +
            options = { keys: [] }
         | 
| 10 17 |  | 
| 11 18 | 
             
            OptionParser.new do |opts|
         | 
| 12 19 | 
             
              opts.banner = 'Usage: config_tool.rb [options]'
         | 
| @@ -23,8 +30,9 @@ OptionParser.new do |opts| | |
| 23 30 | 
             
                options[:command] = :create
         | 
| 24 31 | 
             
              end
         | 
| 25 32 |  | 
| 26 | 
            -
              opts.on('-p', '--print', 'Print configuration details') do
         | 
| 33 | 
            +
              opts.on('-p', '--print [KEYS]', Array, 'Print configuration details for specified keys') do |keys|
         | 
| 27 34 | 
             
                options[:command] = :print
         | 
| 35 | 
            +
                options[:keys] = keys
         | 
| 28 36 | 
             
              end
         | 
| 29 37 |  | 
| 30 38 | 
             
              opts.on_tail('-h', '--help', 'Show this message') do
         | 
| @@ -41,13 +49,13 @@ when :list | |
| 41 49 | 
             
              configurations = Appydave::Tools::Configuration::Config.configurations.map do |name, config|
         | 
| 42 50 | 
             
                { name: name, path: config.config_path, exists: File.exist?(config.config_path) }
         | 
| 43 51 | 
             
              end
         | 
| 44 | 
            -
              tp configurations, :name, :exists, {  | 
| 52 | 
            +
              tp configurations, :name, :exists, { path: { width: 150 } }
         | 
| 45 53 | 
             
            when :create
         | 
| 46 54 | 
             
              Appydave::Tools::Configuration::Config.configure
         | 
| 47 55 | 
             
              Appydave::Tools::Configuration::Config.save
         | 
| 48 56 | 
             
            when :print
         | 
| 49 57 | 
             
              Appydave::Tools::Configuration::Config.configure
         | 
| 50 | 
            -
              Appydave::Tools::Configuration::Config.print
         | 
| 58 | 
            +
              Appydave::Tools::Configuration::Config.print(*options[:keys])
         | 
| 51 59 | 
             
            else
         | 
| 52 60 | 
             
              puts 'No valid command provided. Use --help for usage information.'
         | 
| 53 61 | 
             
            end
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            # Bank reconciliation
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            [ChatGPT conversation](https://chatgpt.com/c/5d382562-95e5-4243-9b74-c3807d363486)
         | 
| 4 | 
            +
             | 
| 5 | 
            +
             | 
| 6 | 
            +
            ## Code structure
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            ```bash
         | 
| 9 | 
            +
            ├─ lib
         | 
| 10 | 
            +
            │ ├─ appydave
         | 
| 11 | 
            +
            │ │ └─ tools
         | 
| 12 | 
            +
            │ │   ├─ bank_reconciliation
         | 
| 13 | 
            +
            │ │   │ ├─ clean
         | 
| 14 | 
            +
            │ │   │ │ ├─ read_transactions.rb
         | 
| 15 | 
            +
            │ │   │ │ ├─ transaction_cleaner.rb
         | 
| 16 | 
            +
            │ │   │ ├─ models
         | 
| 17 | 
            +
            │ │   │ │ ├─ raw_transaction.rb
         | 
| 18 | 
            +
            │ │   │ │ └─ reconciled_transaction.rb
         | 
| 19 | 
            +
            │ │   └─ configuration
         | 
| 20 | 
            +
            │ │     └─ models
         | 
| 21 | 
            +
            │ │       └─ bank_reconciliation_config.rb
         | 
| 22 | 
            +
            └─ spec
         | 
| 23 | 
            +
              ├─ appydave
         | 
| 24 | 
            +
              │ ├─ tools
         | 
| 25 | 
            +
              │ │ ├─ bank_reconciliation
         | 
| 26 | 
            +
              │ │ │ ├─ clean
         | 
| 27 | 
            +
              │ │ │ │ ├─ read_transactions_spec.rb
         | 
| 28 | 
            +
              │ │ │ ├─ models
         | 
| 29 | 
            +
              │ │ │ │ └─ raw_transaction_spec.rb
         | 
| 30 | 
            +
              │ │ └─ configuration
         | 
| 31 | 
            +
              │ │   └─ models
         | 
| 32 | 
            +
              │ │     └─ bank_reconciliation_config_spec.rb
         | 
| 33 | 
            +
              └─ fixtures
         | 
| 34 | 
            +
                └─ bank-reconciliation
         | 
| 35 | 
            +
                  └─ bank-west.csv
         | 
| 36 | 
            +
            ```
         | 
| @@ -0,0 +1,95 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Appydave
         | 
| 4 | 
            +
              module Tools
         | 
| 5 | 
            +
                module BankReconciliation
         | 
| 6 | 
            +
                  module Clean
         | 
| 7 | 
            +
                    # Clean transactions
         | 
| 8 | 
            +
                    class CleanTransactions
         | 
| 9 | 
            +
                      include Appydave::Tools::Configuration::Configurable
         | 
| 10 | 
            +
                      include KLog::Logging
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                      attr_reader :transaction_folder
         | 
| 13 | 
            +
                      attr_reader :transactions
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                      # (config_file)
         | 
| 16 | 
            +
                      def initialize(transaction_folder: '/Volumes/Expansion/Sync/bank-reconciliation/original-transactions')
         | 
| 17 | 
            +
                        @transaction_folder = transaction_folder
         | 
| 18 | 
            +
                      end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                      def clean_transactions(input_globs, _output_file)
         | 
| 21 | 
            +
                        raw_transactions = grab_raw_transactions(input_globs)
         | 
| 22 | 
            +
                        transactions, duplicates_count = deduplicate(raw_transactions)
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                        transactions = Mapper.new.map(transactions)
         | 
| 25 | 
            +
                        # tp transactions
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                        log.kv 'Deduped consolidated transactions', duplicates_count if duplicates_count.positive?
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                        # transactions = normalize(transactions)
         | 
| 30 | 
            +
                        # save_to_csv(transactions, output_file)
         | 
| 31 | 
            +
                        @transactions = transactions
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                      private
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                      def grab_raw_transactions(input_globs)
         | 
| 37 | 
            +
                        original_dir = Dir.pwd
         | 
| 38 | 
            +
                        transactions = []
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                        begin
         | 
| 41 | 
            +
                          Dir.chdir(transaction_folder)
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                          input_globs.each do |glob|
         | 
| 44 | 
            +
                            Dir.glob(glob).each do |file|
         | 
| 45 | 
            +
                              raw_transactions = ReadTransactions.new(file).read
         | 
| 46 | 
            +
                              deduped_transactions, duplicates_count = deduplicate(raw_transactions)
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                              if duplicates_count.positive?
         | 
| 49 | 
            +
                                log.kv 'Duplicates count', duplicates_count
         | 
| 50 | 
            +
                                log.kv 'File', file
         | 
| 51 | 
            +
                              end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                              transactions += deduped_transactions
         | 
| 54 | 
            +
                            end
         | 
| 55 | 
            +
                          end
         | 
| 56 | 
            +
                        ensure
         | 
| 57 | 
            +
                          Dir.chdir(original_dir)
         | 
| 58 | 
            +
                        end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                        transactions
         | 
| 61 | 
            +
                      end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                      def deduplicate(transactions)
         | 
| 64 | 
            +
                        unique_transactions = transactions.uniq do |transaction|
         | 
| 65 | 
            +
                          [
         | 
| 66 | 
            +
                            transaction.bsb_number,
         | 
| 67 | 
            +
                            transaction.account_number,
         | 
| 68 | 
            +
                            transaction.transaction_date,
         | 
| 69 | 
            +
                            transaction.narration,
         | 
| 70 | 
            +
                            transaction.cheque_number,
         | 
| 71 | 
            +
                            transaction.debit,
         | 
| 72 | 
            +
                            transaction.credit,
         | 
| 73 | 
            +
                            transaction.balance,
         | 
| 74 | 
            +
                            transaction.transaction_type
         | 
| 75 | 
            +
                          ]
         | 
| 76 | 
            +
                        end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                        duplicates = transactions.size - unique_transactions.size
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                        [unique_transactions, duplicates]
         | 
| 81 | 
            +
                      end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                      def save_to_csv(transactions, output_file)
         | 
| 84 | 
            +
                        CSV.open(output_file, 'w') do |csv|
         | 
| 85 | 
            +
                          csv << ReconciledTransaction.csv_headers
         | 
| 86 | 
            +
                          transactions.each do |transaction|
         | 
| 87 | 
            +
                            csv << transaction.to_csv_row
         | 
| 88 | 
            +
                          end
         | 
| 89 | 
            +
                        end
         | 
| 90 | 
            +
                      end
         | 
| 91 | 
            +
                    end
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
              end
         | 
| 95 | 
            +
            end
         | 
| @@ -0,0 +1,110 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Appydave
         | 
| 4 | 
            +
              module Tools
         | 
| 5 | 
            +
                module BankReconciliation
         | 
| 6 | 
            +
                  module Clean
         | 
| 7 | 
            +
                    # Map transactions to chart of accounts and bank accounts
         | 
| 8 | 
            +
                    class Mapper
         | 
| 9 | 
            +
                      include Appydave::Tools::Configuration::Configurable
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                      # "bank_accounts": [
         | 
| 12 | 
            +
                      #   {
         | 
| 13 | 
            +
                      #     "account_number": "5435 6859 0116 7736",
         | 
| 14 | 
            +
                      #     "bsb": "",
         | 
| 15 | 
            +
                      #     "name": "Mastercard",
         | 
| 16 | 
            +
                      #     "platform": "Bankwest"
         | 
| 17 | 
            +
                      #   },
         | 
| 18 | 
            +
                      #   {
         | 
| 19 | 
            +
                      #     "account_number": "303-092",
         | 
| 20 | 
            +
                      #     "bsb": "1361644",
         | 
| 21 | 
            +
                      #     "name": "atcall",
         | 
| 22 | 
            +
                      #     "platform": "Bankwest"
         | 
| 23 | 
            +
                      #   },
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                      def map(transactions)
         | 
| 26 | 
            +
                        transactions.map do |original_transaction|
         | 
| 27 | 
            +
                          transaction = original_transaction.dup
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                          transaction = map_chart_of_account(transaction)
         | 
| 30 | 
            +
                          map_bank_account(transaction)
         | 
| 31 | 
            +
                        end
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                      private
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                      def map_chart_of_account(transaction)
         | 
| 37 | 
            +
                        equality_match(transaction) ||
         | 
| 38 | 
            +
                          trigram_match(transaction, 0.9, '90%') ||
         | 
| 39 | 
            +
                          trigram_match(transaction, 0.8, '80%') ||
         | 
| 40 | 
            +
                          trigram_match(transaction, 0.7, '70%') ||
         | 
| 41 | 
            +
                          trigram_match(transaction, 0.6, '60%') ||
         | 
| 42 | 
            +
                          trigram_match(transaction, 0.5, '50%') ||
         | 
| 43 | 
            +
                          transaction
         | 
| 44 | 
            +
                      end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                      def map_bank_account(transaction)
         | 
| 47 | 
            +
                        bank_account = config.bank_reconciliation.get_bank_account(transaction.account_number, transaction.bsb_number)
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                        if bank_account
         | 
| 50 | 
            +
                          transaction.account_name = bank_account.name
         | 
| 51 | 
            +
                          transaction.platform = bank_account.platform
         | 
| 52 | 
            +
                        end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                        transaction
         | 
| 55 | 
            +
                      end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                      def equality_match(transaction)
         | 
| 58 | 
            +
                        coa = config.bank_reconciliation.chart_of_accounts.find do |chart_of_account|
         | 
| 59 | 
            +
                          chart_of_account.narration.to_s.delete(' ') == transaction.narration.delete(' ')
         | 
| 60 | 
            +
                        end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                        return nil unless coa
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                        transaction.coa_match_type = 'equality'
         | 
| 65 | 
            +
                        transaction.coa_code = coa.code
         | 
| 66 | 
            +
                        transaction
         | 
| 67 | 
            +
                      end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                      def trigram_match(transaction, score_threshold, match_type)
         | 
| 70 | 
            +
                        scored_transactions = config.bank_reconciliation.chart_of_accounts.map do |coa|
         | 
| 71 | 
            +
                          {
         | 
| 72 | 
            +
                            coa: coa,
         | 
| 73 | 
            +
                            score: compare(coa.narration, transaction.narration)
         | 
| 74 | 
            +
                          }
         | 
| 75 | 
            +
                        end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                        scored_transactions.sort_by! { |t| t[:score] }.reverse!
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                        best = scored_transactions.first
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                        return nil unless best
         | 
| 82 | 
            +
                        return nil if best[:score] < score_threshold
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                        coa = best[:coa]
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                        transaction.coa_match_type = match_type
         | 
| 87 | 
            +
                        transaction.coa_code = coa.code
         | 
| 88 | 
            +
                        transaction
         | 
| 89 | 
            +
                      end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                      def compare(text1, text2)
         | 
| 92 | 
            +
                        text1_trigs = trigramify(text1)
         | 
| 93 | 
            +
                        text2_trigs = trigramify(text2)
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                        all_cnt = (text1_trigs | text2_trigs).size
         | 
| 96 | 
            +
                        same_cnt = (text1_trigs & text2_trigs).size
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                        same_cnt.to_f / all_cnt
         | 
| 99 | 
            +
                      end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                      def trigramify(text)
         | 
| 102 | 
            +
                        trigs = []
         | 
| 103 | 
            +
                        text.chars.each_cons(3) { |v| trigs << v.join }
         | 
| 104 | 
            +
                        trigs
         | 
| 105 | 
            +
                      end
         | 
| 106 | 
            +
                    end
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
              end
         | 
| 110 | 
            +
            end
         | 
| @@ -0,0 +1,62 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Appydave
         | 
| 4 | 
            +
              module Tools
         | 
| 5 | 
            +
                module BankReconciliation
         | 
| 6 | 
            +
                  module Clean
         | 
| 7 | 
            +
                    # Read transactions from a CSV file
         | 
| 8 | 
            +
                    class ReadTransactions
         | 
| 9 | 
            +
                      attr_reader :platform
         | 
| 10 | 
            +
                      attr_reader :transactions
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                      def initialize(file)
         | 
| 13 | 
            +
                        @file = file
         | 
| 14 | 
            +
                      end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                      def read
         | 
| 17 | 
            +
                        csv_lines = File.read(@file).lines
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                        @platform = detect_platform(csv_lines)
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                        case platform
         | 
| 22 | 
            +
                        when :bankwest
         | 
| 23 | 
            +
                          read_bankwest(csv_lines)
         | 
| 24 | 
            +
                        end
         | 
| 25 | 
            +
                      end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                      private
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                      def read_bankwest(csv_lines)
         | 
| 30 | 
            +
                        @transactions = []
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                        # Skip the header line and parse each subsequent line
         | 
| 33 | 
            +
                        CSV.parse(csv_lines.join, headers: true).each do |row|
         | 
| 34 | 
            +
                          transaction = Models::Transaction.new(
         | 
| 35 | 
            +
                            bsb_number: row['BSB Number'],
         | 
| 36 | 
            +
                            account_number: row['Account Number'],
         | 
| 37 | 
            +
                            transaction_date: row['Transaction Date'],
         | 
| 38 | 
            +
                            narration: row['Narration'],
         | 
| 39 | 
            +
                            cheque_number: row['Cheque Number'],
         | 
| 40 | 
            +
                            debit: row['Debit'],
         | 
| 41 | 
            +
                            credit: row['Credit'],
         | 
| 42 | 
            +
                            balance: row['Balance'],
         | 
| 43 | 
            +
                            transaction_type: row['Transaction Type']
         | 
| 44 | 
            +
                          )
         | 
| 45 | 
            +
                          @transactions << transaction
         | 
| 46 | 
            +
                        end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                        @transactions
         | 
| 49 | 
            +
                      end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                      # For bankwest the first row is the CSV will look like:
         | 
| 52 | 
            +
                      # BSB Number,Account Number,Transaction Date,Narration,Cheque Number,Debit,Credit,Balance,Transaction Type
         | 
| 53 | 
            +
                      def detect_platform(csv_lines)
         | 
| 54 | 
            +
                        return :bankwest if csv_lines.first.start_with?('BSB Number,Account Number,Transaction Date,Narration,Cheque Number,Debit,Credit,Balance,Transaction Type')
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                        raise Appydave::Tools::Error, 'Unknown platform'
         | 
| 57 | 
            +
                      end
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
            end
         | 
| @@ -0,0 +1,55 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Appydave
         | 
| 4 | 
            +
              module Tools
         | 
| 5 | 
            +
                module BankReconciliation
         | 
| 6 | 
            +
                  module Models
         | 
| 7 | 
            +
                    # Unified transaction model for raw and reconciled data
         | 
| 8 | 
            +
                    class Transaction
         | 
| 9 | 
            +
                      attr_accessor :bsb_number,
         | 
| 10 | 
            +
                                    :account_number,
         | 
| 11 | 
            +
                                    :transaction_date,
         | 
| 12 | 
            +
                                    :narration,
         | 
| 13 | 
            +
                                    :cheque_number,
         | 
| 14 | 
            +
                                    :debit,
         | 
| 15 | 
            +
                                    :credit,
         | 
| 16 | 
            +
                                    :balance,
         | 
| 17 | 
            +
                                    :transaction_type,
         | 
| 18 | 
            +
                                    :platform,
         | 
| 19 | 
            +
                                    :coa_code,
         | 
| 20 | 
            +
                                    :coa_match_type,
         | 
| 21 | 
            +
                                    :account_name
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                      def initialize(bsb_number: nil,
         | 
| 24 | 
            +
                                     account_number: nil,
         | 
| 25 | 
            +
                                     transaction_date: nil,
         | 
| 26 | 
            +
                                     narration: nil,
         | 
| 27 | 
            +
                                     cheque_number: nil,
         | 
| 28 | 
            +
                                     debit: nil,
         | 
| 29 | 
            +
                                     credit: nil,
         | 
| 30 | 
            +
                                     balance: nil,
         | 
| 31 | 
            +
                                     transaction_type: nil,
         | 
| 32 | 
            +
                                     platform: nil,
         | 
| 33 | 
            +
                                     coa_code: nil,
         | 
| 34 | 
            +
                                     coa_match_type: nil,
         | 
| 35 | 
            +
                                     account_name: nil)
         | 
| 36 | 
            +
                        @bsb_number = bsb_number&.strip
         | 
| 37 | 
            +
                        @account_number = account_number&.strip
         | 
| 38 | 
            +
                        @transaction_date = transaction_date&.strip
         | 
| 39 | 
            +
                        @transaction_date = Date.strptime(@transaction_date, '%d/%m/%Y')
         | 
| 40 | 
            +
                        @narration = narration&.gsub(/\s{2,}/, ' ')&.strip
         | 
| 41 | 
            +
                        @cheque_number = cheque_number&.strip
         | 
| 42 | 
            +
                        @debit = debit&.strip
         | 
| 43 | 
            +
                        @credit = credit&.strip
         | 
| 44 | 
            +
                        @balance = balance&.strip
         | 
| 45 | 
            +
                        @transaction_type = transaction_type&.strip
         | 
| 46 | 
            +
                        @platform = platform
         | 
| 47 | 
            +
                        @coa_code = coa_code
         | 
| 48 | 
            +
                        @coa_match_type = coa_match_type
         | 
| 49 | 
            +
                        @account_name = account_name
         | 
| 50 | 
            +
                      end
         | 
| 51 | 
            +
                    end
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
            end
         | 
| @@ -63,9 +63,25 @@ module Appydave | |
| 63 63 | 
             
                        configurations.each_value(&:debug)
         | 
| 64 64 | 
             
                      end
         | 
| 65 65 |  | 
| 66 | 
            -
                      def print
         | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 66 | 
            +
                      # def print
         | 
| 67 | 
            +
                      #   log.kv 'Configuration Path', config_path
         | 
| 68 | 
            +
                      #   configurations.each_value(&:print)
         | 
| 69 | 
            +
                      # end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                      def print(*keys)
         | 
| 72 | 
            +
                        if keys.empty?
         | 
| 73 | 
            +
                          keys = configurations.keys
         | 
| 74 | 
            +
                        else
         | 
| 75 | 
            +
                          keys.map!(&:to_sym)
         | 
| 76 | 
            +
                        end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                        keys.each do |key|
         | 
| 79 | 
            +
                          if configurations[key]
         | 
| 80 | 
            +
                            configurations[key].print
         | 
| 81 | 
            +
                          else
         | 
| 82 | 
            +
                            log.error "Configuration not available: #{key}"
         | 
| 83 | 
            +
                          end
         | 
| 84 | 
            +
                        end
         | 
| 69 85 | 
             
                      end
         | 
| 70 86 |  | 
| 71 87 | 
             
                      private
         | 
| @@ -6,6 +6,7 @@ module Appydave | |
| 6 6 | 
             
                  module Models
         | 
| 7 7 | 
             
                    # Bank reconciliation configuration
         | 
| 8 8 | 
             
                    class BankReconciliationConfig < ConfigBase
         | 
| 9 | 
            +
                      # def
         | 
| 9 10 | 
             
                      # Retrieve all bank accounts
         | 
| 10 11 | 
             
                      def bank_accounts
         | 
| 11 12 | 
             
                        data['bank_accounts'].map do |account|
         | 
| @@ -34,7 +35,13 @@ module Appydave | |
| 34 35 | 
             
                      end
         | 
| 35 36 |  | 
| 36 37 | 
             
                      def print
         | 
| 37 | 
            -
                        log. | 
| 38 | 
            +
                        log.subheading 'Bank Reconciliation - Accounts'
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                        tp bank_accounts, :account_number, :bsb, :name, :bank
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                        log.subheading 'Bank Reconciliation - Chart of Accounts'
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                        tp chart_of_accounts, :code, :narration
         | 
| 38 45 | 
             
                      end
         | 
| 39 46 |  | 
| 40 47 | 
             
                      private
         | 
| @@ -48,13 +55,13 @@ module Appydave | |
| 48 55 |  | 
| 49 56 | 
             
                      # Inner class to represent a bank account
         | 
| 50 57 | 
             
                      class BankAccount
         | 
| 51 | 
            -
                        attr_accessor :account_number, :bsb, :name, : | 
| 58 | 
            +
                        attr_accessor :account_number, :bsb, :name, :platform
         | 
| 52 59 |  | 
| 53 60 | 
             
                        def initialize(data)
         | 
| 54 61 | 
             
                          @account_number = data['account_number']
         | 
| 55 62 | 
             
                          @bsb = data['bsb']
         | 
| 56 63 | 
             
                          @name = data['name']
         | 
| 57 | 
            -
                          @ | 
| 64 | 
            +
                          @platform = data['platform']
         | 
| 58 65 | 
             
                        end
         | 
| 59 66 |  | 
| 60 67 | 
             
                        def to_h
         | 
| @@ -62,7 +69,7 @@ module Appydave | |
| 62 69 | 
             
                            'account_number' => @account_number,
         | 
| 63 70 | 
             
                            'bsb' => @bsb,
         | 
| 64 71 | 
             
                            'name' => @name,
         | 
| 65 | 
            -
                            ' | 
| 72 | 
            +
                            'platform' => @platform
         | 
| 66 73 | 
             
                          }
         | 
| 67 74 | 
             
                        end
         | 
| 68 75 | 
             
                      end
         | 
| @@ -39,11 +39,30 @@ module Appydave | |
| 39 39 | 
             
                      def print
         | 
| 40 40 | 
             
                        log.heading 'Channel Configuration'
         | 
| 41 41 |  | 
| 42 | 
            -
                         | 
| 42 | 
            +
                        print_channels = channels.map do |channel|
         | 
| 43 | 
            +
                          {
         | 
| 44 | 
            +
                            key: channel.key,
         | 
| 45 | 
            +
                            code: channel.code,
         | 
| 46 | 
            +
                            name: channel.name,
         | 
| 47 | 
            +
                            youtube_handle: channel.youtube_handle,
         | 
| 48 | 
            +
                            content_projects: print_location(channel.locations.content_projects),
         | 
| 49 | 
            +
                            video_projects: print_location(channel.locations.video_projects),
         | 
| 50 | 
            +
                            published_projects: print_location(channel.locations.published_projects),
         | 
| 51 | 
            +
                            abandoned_projects: print_location(channel.locations.abandoned_projects)
         | 
| 52 | 
            +
                          }
         | 
| 53 | 
            +
                        end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                        tp print_channels, :key, :code, :name, :youtube_handle, :content_projects, :video_projects, :published_projects, :abandoned_projects
         | 
| 43 56 | 
             
                      end
         | 
| 44 57 |  | 
| 45 58 | 
             
                      private
         | 
| 46 59 |  | 
| 60 | 
            +
                      def print_location(location)
         | 
| 61 | 
            +
                        return 'Not Set' unless location
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                        File.exist?(location) ? 'TRUE' : 'false'
         | 
| 64 | 
            +
                      end
         | 
| 65 | 
            +
             | 
| 47 66 | 
             
                      def default_data
         | 
| 48 67 | 
             
                        { 'channels' => {} }
         | 
| 49 68 | 
             
                      end
         | 
    
        data/lib/appydave/tools.rb
    CHANGED
    
    | @@ -1,6 +1,7 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require 'clipboard'
         | 
| 4 | 
            +
            require 'csv'
         | 
| 4 5 | 
             
            require 'fileutils'
         | 
| 5 6 | 
             
            require 'json'
         | 
| 6 7 | 
             
            require 'open3'
         | 
| @@ -19,6 +20,10 @@ require 'appydave/tools/configuration/models/settings_config' | |
| 19 20 | 
             
            require 'appydave/tools/configuration/models/bank_reconciliation_config'
         | 
| 20 21 | 
             
            require 'appydave/tools/configuration/models/channels_config'
         | 
| 21 22 | 
             
            require 'appydave/tools/name_manager/project_name'
         | 
| 23 | 
            +
            require 'appydave/tools/bank_reconciliation/clean/clean_transactions'
         | 
| 24 | 
            +
            require 'appydave/tools/bank_reconciliation/clean/read_transactions'
         | 
| 25 | 
            +
            require 'appydave/tools/bank_reconciliation/clean/mapper'
         | 
| 26 | 
            +
            require 'appydave/tools/bank_reconciliation/models/transaction'
         | 
| 22 27 |  | 
| 23 28 | 
             
            Appydave::Tools::Configuration::Config.set_default do |config|
         | 
| 24 29 | 
             
              config.config_path = File.expand_path('~/.config/appydave')
         | 
    
        data/package-lock.json
    CHANGED
    
    | @@ -1,12 +1,12 @@ | |
| 1 1 | 
             
            {
         | 
| 2 2 | 
             
              "name": "appydave-tools",
         | 
| 3 | 
            -
              "version": "0. | 
| 3 | 
            +
              "version": "0.7.0",
         | 
| 4 4 | 
             
              "lockfileVersion": 3,
         | 
| 5 5 | 
             
              "requires": true,
         | 
| 6 6 | 
             
              "packages": {
         | 
| 7 7 | 
             
                "": {
         | 
| 8 8 | 
             
                  "name": "appydave-tools",
         | 
| 9 | 
            -
                  "version": "0. | 
| 9 | 
            +
                  "version": "0.7.0",
         | 
| 10 10 | 
             
                  "devDependencies": {
         | 
| 11 11 | 
             
                    "@klueless-js/semantic-release-rubygem": "github:klueless-js/semantic-release-rubygem",
         | 
| 12 12 | 
             
                    "@semantic-release/changelog": "^6.0.3",
         | 
    
        data/package.json
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: appydave-tools
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.7.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - David Cruwys
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2024-05- | 
| 11 | 
            +
            date: 2024-05-29 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: clipboard
         | 
| @@ -24,6 +24,20 @@ dependencies: | |
| 24 24 | 
             
                - - "~>"
         | 
| 25 25 | 
             
                  - !ruby/object:Gem::Version
         | 
| 26 26 | 
             
                    version: '1'
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: csv
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - "~>"
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: '3'
         | 
| 34 | 
            +
              type: :runtime
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - "~>"
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: '3'
         | 
| 27 41 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 28 42 | 
             
              name: dotenv
         | 
| 29 43 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -86,12 +100,18 @@ files: | |
| 86 100 | 
             
            - LICENSE.txt
         | 
| 87 101 | 
             
            - README.md
         | 
| 88 102 | 
             
            - Rakefile
         | 
| 103 | 
            +
            - bin/bank_reconciliation.rb
         | 
| 89 104 | 
             
            - bin/configuration.rb
         | 
| 90 105 | 
             
            - bin/console
         | 
| 91 106 | 
             
            - bin/gpt_context.rb
         | 
| 92 107 | 
             
            - bin/setup
         | 
| 93 108 | 
             
            - images.log
         | 
| 94 109 | 
             
            - lib/appydave/tools.rb
         | 
| 110 | 
            +
            - lib/appydave/tools/bank_reconciliation/_doc.md
         | 
| 111 | 
            +
            - lib/appydave/tools/bank_reconciliation/clean/clean_transactions.rb
         | 
| 112 | 
            +
            - lib/appydave/tools/bank_reconciliation/clean/mapper.rb
         | 
| 113 | 
            +
            - lib/appydave/tools/bank_reconciliation/clean/read_transactions.rb
         | 
| 114 | 
            +
            - lib/appydave/tools/bank_reconciliation/models/transaction.rb
         | 
| 95 115 | 
             
            - lib/appydave/tools/configuration/_doc.md
         | 
| 96 116 | 
             
            - lib/appydave/tools/configuration/config.rb
         | 
| 97 117 | 
             
            - lib/appydave/tools/configuration/configurable.rb
         |