appydave-tools 0.6.1 → 0.8.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: 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