appydave-tools 0.6.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2822e09cd6989d2fc8576f8d6b842e6ad40429d9931ae6bbdb557d4bb3a2991c
4
- data.tar.gz: 4a9bc9044bcde2bd179a66349b5544430fb9fc8cb1a7b7a656fc5ed3ba8bd405
3
+ metadata.gz: e4f0fbf0d8d8efc742e691efe1e128bb49f5a1f1423fe2b5eb7a700d9d606c24
4
+ data.tar.gz: 4a6ea3bba78bbb0c8983f462888e0c7e8c1910706e90bbb9db0bfbad99e47405
5
5
  SHA512:
6
- metadata.gz: 4c619668264809fc3be5b5bab73f360dc0480b7dde5b9e232e336edd62a3f416110dbf59ba705174b6b5f8f158819b01817ccacbb123498ad2e17af1330bcaef
7
- data.tar.gz: 4f4c8c4ffbce01ac24a7860939b13f403e9e729d75cb3d7bd68e17a9c37580f2605db3b9667ab4f43a5236ff5803639721ce2452580884c2fe23a3414f031fd9
6
+ metadata.gz: 85058aef1eccdf67319578d1d4c929a17da0143492ba91095ab7e09702f12648174a17bcb611ab5e2951ab500386b98a7f020c5695772c488ebd782bc275e3a6
7
+ data.tar.gz: fb4b852d1dff53cb256c190e586546d5ee2bf13a675da10b0d25c6abc4d04588d541541763bff650609816677e7e3fe407c9920508dcd843a8ab19ac6c2943ab
data/.rubocop.yml CHANGED
@@ -45,6 +45,9 @@ RSpec/ExampleLength:
45
45
 
46
46
  Metrics/MethodLength:
47
47
  Max: 25
48
+ Exclude:
49
+ - "**/spec/**/*"
50
+ - "bin/*.rb"
48
51
 
49
52
  Layout/LineLength:
50
53
  Max: 200
@@ -80,6 +83,7 @@ Style/EmptyMethod:
80
83
  Metrics/ParameterLists:
81
84
  Exclude:
82
85
  - "**/spec/**/*"
86
+ - "**/models/**/*"
83
87
  Layout/EmptyLineBetweenDefs:
84
88
  Exclude:
85
89
  - "**/spec/**/*"
@@ -118,3 +122,17 @@ RSpec/PendingWithoutReason:
118
122
 
119
123
  Metrics/AbcSize:
120
124
  Max: 25
125
+ Exclude:
126
+ - "bin/*"
127
+ - "lib/appydave/tools/subtitle_master/clean.rb"
128
+ Metrics/CyclomaticComplexity:
129
+ Exclude:
130
+ - "**/models/**/*"
131
+ - "lib/appydave/tools/bank_reconciliation/clean/mapper.rb"
132
+ - "lib/appydave/tools/subtitle_master/clean.rb"
133
+ Metrics/PerceivedComplexity:
134
+ Exclude:
135
+ - "**/models/**/*"
136
+ - "lib/appydave/tools/subtitle_master/clean.rb"
137
+ RSpec/MultipleMemoizedHelpers:
138
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [0.7.0](https://github.com/klueless-io/appydave-tools/compare/v0.6.1...v0.7.0) (2024-05-29)
2
+
3
+
4
+ ### Features
5
+
6
+ * new tool for doing bank reconciliations with chart of account matching ([9b82605](https://github.com/klueless-io/appydave-tools/commit/9b8260571f6046470d5963354ee1c80e493a0f28))
7
+
8
+ ## [0.6.1](https://github.com/klueless-io/appydave-tools/compare/v0.6.0...v0.6.1) (2024-05-26)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * improved configuration printing ([89d769d](https://github.com/klueless-io/appydave-tools/commit/89d769d0741fc75b44db90931cf981feea83027f))
14
+
1
15
  # [0.6.0](https://github.com/klueless-io/appydave-tools/compare/v0.5.0...v0.6.0) (2024-05-26)
2
16
 
3
17
 
@@ -0,0 +1,124 @@
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 = { include: [] }
36
+ OptionParser.new do |opts|
37
+ opts.banner = 'Usage: bank_reconciliation.rb clean [options]'
38
+
39
+ opts.on('-i', '--include PATTERN', 'GLOB pattern for source transaction files') do |v|
40
+ options[:include] << v
41
+ end
42
+
43
+ opts.on('-f', '--transaction FOLDER', 'Transaction CSV folder where original banking CSV files are stored') do |v|
44
+ options[:transaction_folder] = v
45
+ end
46
+
47
+ opts.on('-o', '--output FILE', 'Output CSV file name') do |v|
48
+ options[:output] = v
49
+ end
50
+
51
+ opts.on_tail('-h', '--help', 'Show this message') do
52
+ puts opts
53
+ exit
54
+ end
55
+ end.parse!(args)
56
+
57
+ transaction_folder = options[:transaction_folder] || '/default/transaction/folder'
58
+ output_file = options[:output] || 'clean_transactions.csv'
59
+ include_patterns = options[:include].empty? ? ['*'] : options[:include]
60
+
61
+ puts "Cleaning transactions with options: #{options}"
62
+
63
+ # Ensure the clean directory exists
64
+ clean_dir = File.dirname(output_file)
65
+ FileUtils.mkdir_p(clean_dir)
66
+
67
+ # Initialize the CleanTransactions class and process the files
68
+ cleaner = Appydave::Tools::BankReconciliation::Clean::CleanTransactions.new(transaction_folder: transaction_folder)
69
+ cleaner.clean_transactions(include_patterns, output_file)
70
+
71
+ puts "Cleaning transactions with options: #{options}"
72
+ end
73
+
74
+ def process_transactions(args)
75
+ options = {}
76
+ OptionParser.new do |opts|
77
+ opts.banner = 'Usage: bank_reconciliation.rb process [options]'
78
+ opts.on('-i', '--input FILE', 'Input CSV file with transactions') { |v| options[:input] = v }
79
+ opts.on_tail('-h', '--help', 'Show this message') do
80
+ puts opts
81
+ exit
82
+ end
83
+ end.parse!(args)
84
+
85
+ # Implement processing transactions with chart of accounts lookup
86
+ puts "Processing transactions with options: #{options}"
87
+ end
88
+
89
+ def filter_transactions(args)
90
+ options = {}
91
+ OptionParser.new do |opts|
92
+ opts.banner = 'Usage: bank_reconciliation.rb filter [options]'
93
+ opts.on('-i', '--input FILE', 'Input CSV file with processed transactions') { |v| options[:input] = v }
94
+ opts.on('-y', '--year YEAR', 'Filter by financial year') { |v| options[:year] = v }
95
+ opts.on('-b', '--begin DATE', 'Filter by dates greater than or eqaul to DDMMYY') { |v| options[:year] = v }
96
+ opts.on('-e', '--end DATE', 'Filter by dates less than or eqaul to DDMMYY') { |v| options[:year] = v }
97
+ opts.on('-c', '--codes CODES', 'Filter by chart of account codes (comma-separated)') { |v| options[:codes] = v }
98
+ opts.on('-w', '--wild TEXT', 'Wildcard text match') { |v| options[:text] = v }
99
+ opts.on('-d', '--display', 'Display filtered transactions in table format') { |v| options[:display] = v }
100
+ opts.on('-o', '--output FILE', 'Output CSV file name') { |v| options[:output] = v }
101
+ opts.on_tail('-h', '--help', 'Show this message') do
102
+ puts opts
103
+ exit
104
+ end
105
+ end.parse!(args)
106
+
107
+ # Implement filtering of processed transactions
108
+ puts "Filtering transactions with options: #{options}"
109
+ end
110
+
111
+ def print_help
112
+ puts 'Usage: bank_reconciliation.rb [command] [options]'
113
+ puts 'Commands:'
114
+ puts ' clean Clean and normalize transaction files'
115
+ puts ' process Process transaction list via chart of accounts lookup'
116
+ puts ' filter Filter processed transaction list'
117
+ puts "Run 'bank_reconciliation.rb [command] --help' for more information on a command."
118
+ end
119
+ end
120
+
121
+ Appydave::Tools::Configuration::Config.configure
122
+
123
+ BankReconciliationCLI.new.run
124
+ # BankReconciliationCLI.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
5
+
6
+ require 'appydave/tools'
7
+
8
+ # Process command line arguments for SubtitleMaster operations
9
+ class SubtitleMasterCLI
10
+ def initialize
11
+ @commands = {
12
+ 'clean' => method(:clean_subtitles),
13
+ 'correct' => method(:correct_subtitles),
14
+ 'split' => method(:split_subtitles),
15
+ 'highlight' => method(:highlight_subtitles),
16
+ 'image_prompts' => method(:generate_image_prompts)
17
+ }
18
+ end
19
+
20
+ def run
21
+ command, *args = ARGV
22
+ if @commands.key?(command)
23
+ @commands[command].call(args)
24
+ else
25
+ puts "Unknown command: #{command}"
26
+ print_help
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def clean_subtitles(args)
33
+ options = parse_options(args, 'clean')
34
+ cleaner = Appydave::Tools::SubtitleMaster::Clean.new(options[:file])
35
+ result = cleaner.clean
36
+ write_output(result, options[:output])
37
+ end
38
+
39
+ def correct_subtitles(args)
40
+ options = parse_options(args, 'correct')
41
+ corrector = Appydave::Tools::SubtitleMaster::Correct.new(options[:file])
42
+ result = corrector.correct
43
+ write_output(result, options[:output])
44
+ end
45
+
46
+ def split_subtitles(args)
47
+ options = parse_options(args, 'split', %i[words_per_group])
48
+ splitter = Appydave::Tools::SubtitleMaster::Split.new(options[:file], options[:words_per_group])
49
+ result = splitter.split
50
+ write_output(result, options[:output])
51
+ end
52
+
53
+ def highlight_subtitles(args)
54
+ options = parse_options(args, 'highlight')
55
+ highlighter = Appydave::Tools::SubtitleMaster::Highlight.new(options[:file])
56
+ result = highlighter.highlight
57
+ write_output(result, options[:output])
58
+ end
59
+
60
+ def generate_image_prompts(args)
61
+ options = parse_options(args, 'image_prompts')
62
+ image_prompter = Appydave::Tools::SubtitleMaster::ImagePrompts.new(options[:file])
63
+ result = image_prompter.generate_prompts
64
+ write_output(result, options[:output])
65
+ end
66
+
67
+ def parse_options(args, command, extra_options = [])
68
+ options = { file: nil, output: nil }
69
+ OptionParser.new do |opts|
70
+ opts.banner = "Usage: subtitle_master.rb #{command} [options]"
71
+
72
+ opts.on('-f', '--file FILE', 'SRT file to process') { |v| options[:file] = v }
73
+ opts.on('-o', '--output FILE', 'Output file') { |v| options[:output] = v }
74
+
75
+ extra_options.each do |opt|
76
+ case opt
77
+ when :words_per_group
78
+ opts.on('-w', '--words-per-group WORDS', 'Number of words per group for splitting') { |v| options[:words_per_group] = v.to_i }
79
+ end
80
+ end
81
+
82
+ opts.on_tail('-h', '--help', 'Show this message') do
83
+ puts opts
84
+ exit
85
+ end
86
+ end.parse!(args)
87
+
88
+ unless options[:file] && options[:output]
89
+ puts 'Missing required options. Use -h for help.'
90
+ exit
91
+ end
92
+
93
+ options
94
+ end
95
+
96
+ def write_output(result, output_file)
97
+ File.write(output_file, result)
98
+ puts "Processed file written to #{output_file}"
99
+ end
100
+
101
+ def print_help
102
+ puts 'Usage: subtitle_master.rb [command] [options]'
103
+ puts 'Commands:'
104
+ puts ' clean Clean and normalize SRT files'
105
+ puts ' correct Correct common typos and mistranslations in SRT files'
106
+ puts ' split Split subtitle groups based on word count'
107
+ puts ' highlight Highlight power words in subtitles'
108
+ puts ' image_prompts Generate image prompts from subtitle text'
109
+ puts "Run 'subtitle_master.rb [command] --help' for more information on a command."
110
+ end
111
+ end
112
+
113
+ SubtitleMasterCLI.new.run
@@ -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,106 @@
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 :output_folder
14
+ attr_reader :transactions
15
+
16
+ # (config_file)
17
+ def initialize(transaction_folder: nil, output_folder: nil)
18
+ # needs to use config.bank_reconciliation.transaction_folder
19
+ transaction_folder ||= '/Volumes/Expansion/Sync/bank-reconciliation/original-transactions'
20
+ output_folder ||= File.join(transaction_folder, 'clean')
21
+
22
+ @transaction_folder = transaction_folder
23
+ @output_folder = output_folder
24
+ end
25
+
26
+ def clean_transactions(input_globs, output_file)
27
+ raw_transactions = grab_raw_transactions(input_globs)
28
+ transactions, duplicates_count = deduplicate(raw_transactions)
29
+
30
+ transactions = Mapper.new.map(transactions)
31
+
32
+ # tp transactions, Appydave::Tools::BankReconciliation::Models::Transaction.csv_headers
33
+
34
+ log.kv 'Deduped consolidated transactions', duplicates_count if duplicates_count.positive?
35
+
36
+ save_to_csv(transactions, output_file)
37
+
38
+ @transactions = transactions
39
+ end
40
+
41
+ private
42
+
43
+ def grab_raw_transactions(input_globs)
44
+ original_dir = Dir.pwd
45
+ transactions = []
46
+
47
+ begin
48
+ Dir.chdir(transaction_folder)
49
+
50
+ input_globs.each do |glob|
51
+ Dir.glob(glob).each do |file|
52
+ log.kv 'Reading transactions from', file
53
+ raw_transactions = ReadTransactions.new(file).read
54
+ deduped_transactions, duplicates_count = deduplicate(raw_transactions)
55
+
56
+ if duplicates_count.positive?
57
+ log.kv 'Duplicates count', duplicates_count
58
+ log.kv 'File', file
59
+ end
60
+
61
+ transactions += deduped_transactions
62
+ end
63
+ end
64
+ ensure
65
+ Dir.chdir(original_dir)
66
+ end
67
+
68
+ transactions
69
+ end
70
+
71
+ def deduplicate(transactions)
72
+ unique_transactions = transactions.uniq do |transaction|
73
+ [
74
+ transaction.bsb_number,
75
+ transaction.account_number,
76
+ transaction.transaction_date,
77
+ transaction.narration,
78
+ transaction.cheque_number,
79
+ transaction.debit,
80
+ transaction.credit,
81
+ transaction.balance,
82
+ transaction.transaction_type
83
+ ]
84
+ end
85
+
86
+ duplicates = transactions.size - unique_transactions.size
87
+
88
+ [unique_transactions, duplicates]
89
+ end
90
+
91
+ def save_to_csv(transactions, output_file)
92
+ FileUtils.mkdir_p(output_folder)
93
+ output_file = File.join(output_folder, output_file)
94
+
95
+ CSV.open(output_file, 'w') do |csv|
96
+ csv << Appydave::Tools::BankReconciliation::Models::Transaction.csv_headers
97
+ transactions.each do |transaction|
98
+ csv << transaction.to_csv_row
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,136 @@
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
+ start_with_match(transaction) ||
44
+ includes(transaction)
45
+ transaction
46
+ end
47
+
48
+ def map_bank_account(transaction)
49
+ bank_account = config.bank_reconciliation.get_bank_account(transaction.account_number, transaction.bsb_number)
50
+
51
+ if bank_account
52
+ transaction.account_name = bank_account.name
53
+ transaction.platform = bank_account.platform
54
+ end
55
+
56
+ transaction
57
+ end
58
+
59
+ def equality_match(transaction)
60
+ coa = config.bank_reconciliation.chart_of_accounts.find do |chart_of_account|
61
+ chart_of_account.narration.to_s.delete(' ').downcase == transaction.narration.delete(' ').downcase
62
+ end
63
+
64
+ return nil unless coa
65
+
66
+ transaction.coa_match_type = 'equality'
67
+ transaction.coa_code = coa.code
68
+ transaction
69
+ end
70
+
71
+ def start_with_match(transaction)
72
+ coa = config.bank_reconciliation.chart_of_accounts.find do |chart_of_account|
73
+ transaction.narration.to_s.delete(' ').downcase.start_with?(chart_of_account.narration.to_s.downcase)
74
+ end
75
+
76
+ return nil unless coa
77
+
78
+ transaction.coa_match_type = 'starts_with'
79
+ transaction.coa_code = coa.code
80
+ transaction
81
+ end
82
+
83
+ def includes(transaction)
84
+ coa = config.bank_reconciliation.chart_of_accounts.find do |chart_of_account|
85
+ transaction.narration.to_s.delete(' ').downcase.include?(chart_of_account.narration.delete(' ').to_s.downcase)
86
+ end
87
+
88
+ return nil unless coa
89
+
90
+ transaction.coa_match_type = 'includes'
91
+ transaction.coa_code = coa.code
92
+ transaction
93
+ end
94
+
95
+ def trigram_match(transaction, score_threshold, match_type)
96
+ scored_transactions = config.bank_reconciliation.chart_of_accounts.map do |coa|
97
+ {
98
+ coa: coa,
99
+ score: compare(coa.narration, transaction.narration)
100
+ }
101
+ end
102
+
103
+ scored_transactions.sort_by! { |t| t[:score] }.reverse!
104
+
105
+ best = scored_transactions.first
106
+
107
+ return nil unless best
108
+ return nil if best[:score] < score_threshold
109
+
110
+ coa = best[:coa]
111
+
112
+ transaction.coa_match_type = match_type
113
+ transaction.coa_code = coa.code
114
+ transaction
115
+ end
116
+
117
+ def compare(text1, text2)
118
+ text1_trigs = trigramify(text1)
119
+ text2_trigs = trigramify(text2)
120
+
121
+ all_cnt = (text1_trigs | text2_trigs).size
122
+ same_cnt = (text1_trigs & text2_trigs).size
123
+
124
+ same_cnt.to_f / all_cnt
125
+ end
126
+
127
+ def trigramify(text)
128
+ trigs = []
129
+ text.chars.each_cons(3) { |v| trigs << v.join }
130
+ trigs
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,88 @@
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
+ when :bankwest2
25
+ read_bankwest2(csv_lines)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def read_bankwest(csv_lines)
32
+ @transactions = []
33
+
34
+ # Skip the header line and parse each subsequent line
35
+ CSV.parse(csv_lines.join, headers: true).each do |row|
36
+ transaction = Models::Transaction.new(
37
+ bsb_number: row['BSB Number'],
38
+ account_number: row['Account Number'],
39
+ transaction_date: row['Transaction Date'],
40
+ narration: row['Narration'],
41
+ cheque_number: row['Cheque Number'],
42
+ debit: row['Debit'],
43
+ credit: row['Credit'],
44
+ balance: row['Balance'],
45
+ transaction_type: row['Transaction Type']
46
+ )
47
+ @transactions << transaction
48
+ end
49
+
50
+ @transactions
51
+ end
52
+
53
+ def read_bankwest2(csv_lines)
54
+ @transactions = []
55
+
56
+ # Skip the header line and parse each subsequent line
57
+ CSV.parse(csv_lines.join, headers: true).each do |row|
58
+ transaction = Models::Transaction.new(
59
+ bsb_number: row['BSB / Account Number'].split(' - ').first,
60
+ account_number: row['BSB / Account Number'].split(' - ').last,
61
+ transaction_date: row['Transaction Date'],
62
+ narration: row['Narration'],
63
+ cheque_number: row['Cheque Number'],
64
+ debit: row['Debit'],
65
+ credit: row['Credit'],
66
+ balance: row['Balance'],
67
+ transaction_type: row['Transaction Type']
68
+ )
69
+ @transactions << transaction
70
+ end
71
+
72
+ @transactions
73
+ end
74
+
75
+ # For bankwest the first row is the CSV will look like:
76
+ # BSB Number,Account Number,Transaction Date,Narration,Cheque Number,Debit,Credit,Balance,Transaction Type
77
+ def detect_platform(csv_lines)
78
+ return :bankwest if csv_lines.first.start_with?('BSB Number,Account Number,Transaction Date,Narration,Cheque Number,Debit,Credit,Balance,Transaction Type')
79
+ return :bankwest2 if csv_lines.first.start_with?('Account Name,BSB / Account Number,Transaction Date,Narration,Cheque Number,Debit,Credit,Balance,Transaction Type')
80
+
81
+ puts "Unknown platform detected. CSV columns are: #{csv_lines.first.strip}"
82
+ raise Appydave::Tools::Error, 'Unknown platform'
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,91 @@
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
+
52
+ def self.csv_headers
53
+ %i[
54
+ bsb_number
55
+ account_number
56
+ transaction_date
57
+ narration
58
+ cheque_number
59
+ debit
60
+ credit
61
+ balance
62
+ transaction_type
63
+ platform
64
+ coa_code
65
+ coa_match_type
66
+ account_name
67
+ ]
68
+ end
69
+
70
+ def to_csv_row
71
+ [
72
+ @bsb_number,
73
+ @account_number,
74
+ @transaction_date,
75
+ @narration,
76
+ @cheque_number,
77
+ @debit,
78
+ @credit,
79
+ @balance,
80
+ @transaction_type,
81
+ @platform,
82
+ @coa_code,
83
+ @coa_match_type,
84
+ @account_name
85
+ ]
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ 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
@@ -0,0 +1,3 @@
1
+ # Subtitle Master
2
+
3
+ [ChatGPT](https://chatgpt.com/c/f80dfca5-8168-4561-b5c6-8efed8672a88)
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ module SubtitleMaster
6
+ # Clean and normalize subtitles
7
+ class Clean
8
+ def initialize(file_path)
9
+ @file_path = file_path
10
+ end
11
+
12
+ def clean
13
+ content = File.read(@file_path, encoding: 'UTF-8')
14
+ content = remove_underscores(content)
15
+ normalize_lines(content)
16
+ end
17
+
18
+ private
19
+
20
+ def remove_underscores(content)
21
+ content.gsub(%r{</?u>}, '')
22
+ end
23
+
24
+ def normalize_lines(content)
25
+ lines = content.split("\n")
26
+ grouped_subtitles = []
27
+ current_subtitle = { text: '', start_time: nil, end_time: nil }
28
+
29
+ lines.each do |line|
30
+ if line =~ /^\d+$/ || line.strip.empty?
31
+ next
32
+ elsif line =~ /^\d{2}:\d{2}:\d{2},\d{3} --> \d{2}:\d{2}:\d{2},\d{3}$/
33
+ if current_subtitle[:start_time]
34
+ grouped_subtitles << current_subtitle.clone
35
+ current_subtitle = { text: '', start_time: nil, end_time: nil }
36
+ end
37
+
38
+ times = line.split(' --> ')
39
+ current_subtitle[:start_time] = times[0]
40
+ current_subtitle[:end_time] = times[1]
41
+ else
42
+ current_subtitle[:text] += ' ' unless current_subtitle[:text].empty?
43
+ current_subtitle[:text] += line.strip
44
+ end
45
+ end
46
+
47
+ grouped_subtitles << current_subtitle unless current_subtitle[:text].empty?
48
+
49
+ grouped_subtitles = merge_subtitles(grouped_subtitles)
50
+
51
+ build_normalized_content(grouped_subtitles)
52
+ end
53
+
54
+ def merge_subtitles(subtitles)
55
+ merged_subtitles = []
56
+ subtitles.each do |subtitle|
57
+ if merged_subtitles.empty? || merged_subtitles.last[:text] != subtitle[:text]
58
+ merged_subtitles << subtitle
59
+ else
60
+ merged_subtitles.last[:end_time] = subtitle[:end_time]
61
+ end
62
+ end
63
+ merged_subtitles
64
+ end
65
+
66
+ def build_normalized_content(grouped_subtitles)
67
+ normalized_content = []
68
+ grouped_subtitles.each_with_index do |subtitle, index|
69
+ normalized_content << (index + 1).to_s
70
+ normalized_content << "#{subtitle[:start_time]} --> #{subtitle[:end_time]}"
71
+ normalized_content << subtitle[:text]
72
+ normalized_content << ''
73
+ end
74
+
75
+ normalized_content.join("\n")
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Appydave
4
4
  module Tools
5
- VERSION = '0.6.1'
5
+ VERSION = '0.8.0'
6
6
  end
7
7
  end
@@ -1,12 +1,14 @@
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'
7
8
  require 'openai'
8
9
  require 'optparse'
9
10
  require 'k_log'
11
+ require 'pry'
10
12
 
11
13
  require 'appydave/tools/version'
12
14
  require 'appydave/tools/gpt_context/file_collector'
@@ -19,6 +21,12 @@ require 'appydave/tools/configuration/models/settings_config'
19
21
  require 'appydave/tools/configuration/models/bank_reconciliation_config'
20
22
  require 'appydave/tools/configuration/models/channels_config'
21
23
  require 'appydave/tools/name_manager/project_name'
24
+ require 'appydave/tools/bank_reconciliation/clean/clean_transactions'
25
+ require 'appydave/tools/bank_reconciliation/clean/read_transactions'
26
+ require 'appydave/tools/bank_reconciliation/clean/mapper'
27
+ require 'appydave/tools/bank_reconciliation/models/transaction'
28
+
29
+ require 'appydave/tools/subtitle_master/clean'
22
30
 
23
31
  Appydave::Tools::Configuration::Config.set_default do |config|
24
32
  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.8.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.8.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.8.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.8.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-06-06 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,19 @@ 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
108
+ - bin/subtitle_master.rb
93
109
  - images.log
94
110
  - lib/appydave/tools.rb
111
+ - lib/appydave/tools/bank_reconciliation/_doc.md
112
+ - lib/appydave/tools/bank_reconciliation/clean/clean_transactions.rb
113
+ - lib/appydave/tools/bank_reconciliation/clean/mapper.rb
114
+ - lib/appydave/tools/bank_reconciliation/clean/read_transactions.rb
115
+ - lib/appydave/tools/bank_reconciliation/models/transaction.rb
95
116
  - lib/appydave/tools/configuration/_doc.md
96
117
  - lib/appydave/tools/configuration/config.rb
97
118
  - lib/appydave/tools/configuration/configurable.rb
@@ -105,6 +126,8 @@ files:
105
126
  - lib/appydave/tools/gpt_context/file_collector.rb
106
127
  - lib/appydave/tools/name_manager/_doc.md
107
128
  - lib/appydave/tools/name_manager/project_name.rb
129
+ - lib/appydave/tools/subtitle_master/_doc.md
130
+ - lib/appydave/tools/subtitle_master/clean.rb
108
131
  - lib/appydave/tools/version.rb
109
132
  - package-lock.json
110
133
  - package.json