appydave-tools 0.6.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2822e09cd6989d2fc8576f8d6b842e6ad40429d9931ae6bbdb557d4bb3a2991c
4
- data.tar.gz: 4a9bc9044bcde2bd179a66349b5544430fb9fc8cb1a7b7a656fc5ed3ba8bd405
3
+ metadata.gz: 14a6bd2963b58f85f247282a6cf8c537495b9a18300b04c3a3794ef5df4ecaa1
4
+ data.tar.gz: 5ec47924ab4268377a297e1f02f6fa6b4f62207179f21fe8ccdcd69d62cd4f8d
5
5
  SHA512:
6
- metadata.gz: 4c619668264809fc3be5b5bab73f360dc0480b7dde5b9e232e336edd62a3f416110dbf59ba705174b6b5f8f158819b01817ccacbb123498ad2e17af1330bcaef
7
- data.tar.gz: 4f4c8c4ffbce01ac24a7860939b13f403e9e729d75cb3d7bd68e17a9c37580f2605db3b9667ab4f43a5236ff5803639721ce2452580884c2fe23a3414f031fd9
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/**/*"
@@ -118,3 +119,14 @@ RSpec/PendingWithoutReason:
118
119
 
119
120
  Metrics/AbcSize:
120
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,10 @@
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
+
1
8
  # [0.6.0](https://github.com/klueless-io/appydave-tools/compare/v0.5.0...v0.6.0) (2024-05-26)
2
9
 
3
10
 
@@ -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
@@ -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
@@ -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|
@@ -54,13 +55,13 @@ module Appydave
54
55
 
55
56
  # Inner class to represent a bank account
56
57
  class BankAccount
57
- attr_accessor :account_number, :bsb, :name, :bank
58
+ attr_accessor :account_number, :bsb, :name, :platform
58
59
 
59
60
  def initialize(data)
60
61
  @account_number = data['account_number']
61
62
  @bsb = data['bsb']
62
63
  @name = data['name']
63
- @bank = data['bank']
64
+ @platform = data['platform']
64
65
  end
65
66
 
66
67
  def to_h
@@ -68,7 +69,7 @@ module Appydave
68
69
  'account_number' => @account_number,
69
70
  'bsb' => @bsb,
70
71
  'name' => @name,
71
- 'bank' => @bank
72
+ 'platform' => @platform
72
73
  }
73
74
  end
74
75
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Appydave
4
4
  module Tools
5
- VERSION = '0.6.1'
5
+ VERSION = '0.7.0'
6
6
  end
7
7
  end
@@ -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.6.1",
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.6.1",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appydave-tools",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "AppyDave YouTube Automation Tools",
5
5
  "scripts": {
6
6
  "release": "semantic-release"
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.6.1
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-26 00:00:00.000000000 Z
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