reckon 0.9.6 → 0.10.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 +12 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile.lock +1 -1
- data/README.md +2 -0
- data/lib/reckon/app.rb +33 -7
- data/lib/reckon/cosine_similarity.rb +1 -0
- data/lib/reckon/csv_parser.rb +9 -8
- data/lib/reckon/ledger_parser.rb +1 -1
- data/lib/reckon/logger.rb +4 -0
- data/lib/reckon/options.rb +39 -9
- data/lib/reckon/version.rb +1 -1
- data/spec/cosine_training_and_test.rb +2 -1
- data/spec/integration/ask_for_account/expected_output +10 -10
- data/spec/reckon/ledger_parser_spec.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b0141e2c8428b74039967461cb7db1b06fd4acd63edb2db8921ec2c9a2895ec
|
4
|
+
data.tar.gz: 82c6d558f8e030988114db22d9ad59f580c7275a10a019b68c02355ea6639c71
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec2519c7bd438012ae498b4e3a9517d6c63ae3de71bf7237043bdbeb60c589a88180a9171f0bcb5b25f1e7dda35c7f6eb9d99173d6c9905e2bc0913fe244cca6
|
7
|
+
data.tar.gz: 8f3ea58e38dc864cdcbb10b8ecb50f214f0dfa903eef6029b9c48057807d6910de3928b61c7bc4e14051dd9052f01fafcefab9bffca8dc107d516f17e5e4a427
|
data/.rubocop.yml
CHANGED
@@ -18,3 +18,15 @@ Metrics/AbcSize:
|
|
18
18
|
|
19
19
|
Style/NumericPredicate:
|
20
20
|
Enabled: False
|
21
|
+
|
22
|
+
Metrics/PerceivedComplexity:
|
23
|
+
Enabled: False
|
24
|
+
|
25
|
+
Metrics/CyclomaticComplexity:
|
26
|
+
Enabled: False
|
27
|
+
|
28
|
+
Style/FormatString:
|
29
|
+
Enabled: False
|
30
|
+
|
31
|
+
Naming/MethodParameterName:
|
32
|
+
Enabled: False
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v0.10.0](https://github.com/cantino/reckon/tree/v0.10.0) (2024-11-27)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/cantino/reckon/compare/v0.9.6...v0.10.0)
|
6
|
+
|
7
|
+
**Closed issues:**
|
8
|
+
|
9
|
+
- Is it possible to use reckon with only no info about incoming or going out? [\#131](https://github.com/cantino/reckon/issues/131)
|
10
|
+
- Reckon fails immediately with error about uninitialized constant `Readline` [\#129](https://github.com/cantino/reckon/issues/129)
|
11
|
+
|
12
|
+
## [v0.9.6](https://github.com/cantino/reckon/tree/v0.9.6) (2024-03-27)
|
13
|
+
|
14
|
+
[Full Changelog](https://github.com/cantino/reckon/compare/v0.9.5...v0.9.6)
|
15
|
+
|
16
|
+
**Closed issues:**
|
17
|
+
|
18
|
+
- reckon can't learn from a file with the "wrong" date format [\#130](https://github.com/cantino/reckon/issues/130)
|
19
|
+
|
3
20
|
## [v0.9.5](https://github.com/cantino/reckon/tree/v0.9.5) (2024-01-08)
|
4
21
|
|
5
22
|
[Full Changelog](https://github.com/cantino/reckon/compare/v0.9.4...v0.9.5)
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -49,6 +49,8 @@ Learn more:
|
|
49
49
|
Column number of the money columns, starts from 1 (1 or 2 columns)
|
50
50
|
--raw-money
|
51
51
|
Don't format money column (for stocks)
|
52
|
+
--sort DATE|DESC|AMT
|
53
|
+
Sort file by date, description, or amount
|
52
54
|
--date-column 3
|
53
55
|
Column number of the date column, starts from 1
|
54
56
|
--contains-header [N]
|
data/lib/reckon/app.rb
CHANGED
@@ -10,10 +10,11 @@ module Reckon
|
|
10
10
|
|
11
11
|
def initialize(opts = {})
|
12
12
|
self.options = opts
|
13
|
-
LOGGER.level =
|
13
|
+
LOGGER.level = options[:verbose] || Logger::WARN
|
14
14
|
|
15
15
|
self.regexps = {}
|
16
16
|
self.seen = Set.new
|
17
|
+
options[:sort] ||= :date
|
17
18
|
@cli = HighLine.new
|
18
19
|
@csv_parser = CSVParser.new(options)
|
19
20
|
@matcher = CosineSimilarity.new(options)
|
@@ -80,7 +81,7 @@ module Reckon
|
|
80
81
|
|
81
82
|
# Add tokens from account_tokens_file to accounts
|
82
83
|
def extract_account_tokens(subtree, account = nil)
|
83
|
-
if subtree.nil?
|
84
|
+
if subtree.nil? || !subtree
|
84
85
|
puts "Warning: empty #{account} tree"
|
85
86
|
{}
|
86
87
|
elsif subtree.is_a?(Array)
|
@@ -141,6 +142,11 @@ module Reckon
|
|
141
142
|
line2 = [options[:bank_account], row[:pretty_money]]
|
142
143
|
end
|
143
144
|
|
145
|
+
if answer == '~~SKIP~~'
|
146
|
+
LOGGER.info "skipping transaction: #{row}"
|
147
|
+
next
|
148
|
+
end
|
149
|
+
|
144
150
|
finish if %w[quit q].include?(answer)
|
145
151
|
if %w[skip s].include?(answer)
|
146
152
|
interactive_output "Skipping"
|
@@ -168,18 +174,29 @@ module Reckon
|
|
168
174
|
:money => @csv_parser.money_for(index),
|
169
175
|
:description => @csv_parser.description_for(index) }
|
170
176
|
end
|
171
|
-
rows.sort_by
|
177
|
+
rows.sort_by do |n|
|
178
|
+
[n[options[:sort]], -n[:money], n[:description]]
|
179
|
+
end.each do |row|
|
180
|
+
yield row
|
181
|
+
end
|
172
182
|
end
|
173
183
|
|
174
184
|
def print_transaction(rows, fh = $stdout)
|
175
185
|
str = "\n"
|
176
|
-
header = %w[Date Amount Description
|
186
|
+
header = %w[Date Amount Description]
|
187
|
+
header += ["Note"] if rows.map { |r| r[:note] }.any?
|
177
188
|
maxes = header.map(&:length)
|
178
|
-
|
179
|
-
|
189
|
+
rows = rows.map do |r|
|
190
|
+
[r[:pretty_date], r[:pretty_money], r[:description], r[:note]].compact
|
191
|
+
end
|
180
192
|
|
181
193
|
rows.each do |r|
|
182
|
-
r.length.times
|
194
|
+
r.length.times do |i|
|
195
|
+
l = 0
|
196
|
+
l = r[i].length if r[i]
|
197
|
+
maxes[i] ||= 0
|
198
|
+
maxes[i] = l if maxes[i] < l
|
199
|
+
end
|
183
200
|
end
|
184
201
|
|
185
202
|
header.each_with_index do |n, i|
|
@@ -199,6 +216,15 @@ module Reckon
|
|
199
216
|
end
|
200
217
|
|
201
218
|
def ask_account_question(msg, row)
|
219
|
+
# return account token if it matches
|
220
|
+
token_answer = most_specific_regexp_match(row)
|
221
|
+
if token_answer.any?
|
222
|
+
row[:note] = "Matched account token"
|
223
|
+
puts "NOTE: Matched account token"
|
224
|
+
puts token_answer[0]
|
225
|
+
return token_answer[0]
|
226
|
+
end
|
227
|
+
|
202
228
|
possible_answers = suggest(row)
|
203
229
|
LOGGER.info "possible_answers===> #{possible_answers.inspect}"
|
204
230
|
|
data/lib/reckon/csv_parser.rb
CHANGED
@@ -64,13 +64,13 @@ module Reckon
|
|
64
64
|
private
|
65
65
|
|
66
66
|
def filter_csv
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
@columns = new_columns
|
67
|
+
return unless options[:ignore_columns]
|
68
|
+
|
69
|
+
new_columns = []
|
70
|
+
columns.each_with_index do |column, index|
|
71
|
+
new_columns << (options[:ignore_columns].include?(index + 1) ? [''] * column.length : column)
|
73
72
|
end
|
73
|
+
@columns = new_columns
|
74
74
|
end
|
75
75
|
|
76
76
|
def evaluate_columns(cols)
|
@@ -222,7 +222,8 @@ module Reckon
|
|
222
222
|
# convert to a stringio object to handle multi-line fields
|
223
223
|
parser_opts = {
|
224
224
|
col_sep: separator,
|
225
|
-
skip_blanks: true
|
225
|
+
skip_blanks: true,
|
226
|
+
row_sep: :auto
|
226
227
|
}
|
227
228
|
begin
|
228
229
|
rows = CSV.parse(StringIO.new(data), **parser_opts)
|
@@ -235,7 +236,7 @@ module Reckon
|
|
235
236
|
index = data.index("\n", index) + 1 # skip over newline character
|
236
237
|
count += 1
|
237
238
|
end
|
238
|
-
rows = CSV.parse(StringIO.new(data[index
|
239
|
+
rows = CSV.parse(StringIO.new(data[index..]), **parser_opts)
|
239
240
|
rows[0..-footer_lines_to_skip]
|
240
241
|
end
|
241
242
|
end
|
data/lib/reckon/ledger_parser.rb
CHANGED
@@ -175,7 +175,7 @@ module Reckon
|
|
175
175
|
end
|
176
176
|
|
177
177
|
def format_row(row, line1, line2)
|
178
|
-
note = row[:note] ? "\t; row[:note]" : ""
|
178
|
+
note = row[:note] ? "\t; #{row[:note]}" : ""
|
179
179
|
out = "#{row[:pretty_date]}\t#{row[:description]}#{note}\n"
|
180
180
|
out += "\t#{line1.first}\t\t\t#{line1.last}\n"
|
181
181
|
out += "\t#{line2.first}\t\t\t#{line2.last}\n\n"
|
data/lib/reckon/logger.rb
CHANGED
data/lib/reckon/options.rb
CHANGED
@@ -4,7 +4,6 @@ module Reckon
|
|
4
4
|
# Singleton class for parsing command line flags
|
5
5
|
class Options
|
6
6
|
def self.parse_command_line_options(args = ARGV, stdin = $stdin)
|
7
|
-
cli = HighLine.new
|
8
7
|
options = { output_file: $stdout }
|
9
8
|
OptionParser.new do |opts|
|
10
9
|
opts.banner = "Usage: Reckon.rb [options]"
|
@@ -18,8 +17,17 @@ module Reckon
|
|
18
17
|
options[:bank_account] = a
|
19
18
|
end
|
20
19
|
|
21
|
-
|
22
|
-
|
20
|
+
options[:verbose] = Logger::WARN
|
21
|
+
opts.on("-v", "--v", "Run verbosely (show info log messages)") do
|
22
|
+
options[:verbose] = Logger::INFO
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on("", "--verbose", "Run verbosely (show info log messages)") do
|
26
|
+
options[:verbose] = Logger::INFO
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on("", "--vv", "Run very verbosely (show debug log messages)") do
|
30
|
+
options[:verbose] = Logger::DEBUG
|
23
31
|
end
|
24
32
|
|
25
33
|
opts.on("-i", "--inverse", "Use the negative of each amount") do |v|
|
@@ -58,6 +66,19 @@ module Reckon
|
|
58
66
|
options[:raw] = n
|
59
67
|
end
|
60
68
|
|
69
|
+
options[:sort] = :date
|
70
|
+
opts.on("", "--sort DATE|DESC|AMT", "Sort file by date, description, or amount") do |s|
|
71
|
+
if s == 'DESC'
|
72
|
+
options[:sort] = :description
|
73
|
+
elsif s == 'AMT'
|
74
|
+
options[:sort] = :money
|
75
|
+
elsif s == 'DATE'
|
76
|
+
options[:sort] = :date
|
77
|
+
else
|
78
|
+
raise "'#{s}' is not valid. valid sort options are DATE, DESC, AMT"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
61
82
|
opts.on("", "--date-column 3", Integer,
|
62
83
|
"Column number of the date column, starts from 1") do |col|
|
63
84
|
options[:date_column] = col
|
@@ -161,26 +182,35 @@ module Reckon
|
|
161
182
|
options[:string] = stdin.read
|
162
183
|
end
|
163
184
|
|
185
|
+
validate_options(options)
|
186
|
+
|
187
|
+
return options
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.validate_options(options)
|
191
|
+
cli = HighLine.new
|
164
192
|
unless options[:file]
|
165
193
|
options[:file] = cli.ask("What CSV file should I parse? ")
|
166
|
-
|
167
|
-
puts "\
|
168
|
-
puts parser
|
194
|
+
if options[:file].empty?
|
195
|
+
puts "\nERROR: You must provide a CSV file to parse.\n"
|
169
196
|
exit
|
170
197
|
end
|
171
198
|
end
|
172
199
|
|
173
200
|
unless options[:bank_account]
|
174
|
-
|
201
|
+
if options[:unattended]
|
202
|
+
puts "ERROR: Must specify --account in unattended mode"
|
203
|
+
exit
|
204
|
+
end
|
175
205
|
|
176
|
-
options[:bank_account] = cli.ask("What is
|
206
|
+
options[:bank_account] = cli.ask("What is the Ledger account name?\n") do |q|
|
177
207
|
q.readline = true
|
178
208
|
q.validate = /^.{2,}$/
|
179
209
|
q.default = "Assets:Bank:Checking"
|
180
210
|
end
|
181
211
|
end
|
182
212
|
|
183
|
-
return
|
213
|
+
return true
|
184
214
|
end
|
185
215
|
end
|
186
216
|
end
|
data/lib/reckon/version.rb
CHANGED
@@ -8,7 +8,7 @@ ledger_file = ARGV[0]
|
|
8
8
|
account = ARGV[1]
|
9
9
|
seed = ARGV[2] ? ARGV[2].to_i : Random.new_seed
|
10
10
|
|
11
|
-
ledger = Reckon::LedgerParser.new(File.new(ledger_file))
|
11
|
+
ledger = Reckon::LedgerParser.new.parse(File.new(ledger_file))
|
12
12
|
matcher = Reckon::CosineSimilarity.new({})
|
13
13
|
|
14
14
|
train = []
|
@@ -50,3 +50,4 @@ end
|
|
50
50
|
# pp result.compact
|
51
51
|
puts "using #{seed} as random seed"
|
52
52
|
puts "true: #{result.count(nil)} false: #{result.count { |v| !v.nil? }}"
|
53
|
+
puts(result.filter { |v| !v.nil? })
|
@@ -1,11 +1,11 @@
|
|
1
1
|
|
2
|
-
Date | Amount | Description |
|
3
|
-
2003-12-24 | $2,105.00 | CREDIT; Some Company vendorpymt PPD ID: 5KL3832735 |
|
4
|
-
2004-12-24 | -$116.22 | CREDIT; PAYPAL TRANSFER PPD ID: PAYPALSDSL |
|
5
|
-
2005-12-24 | -$0.96 | DEBIT; WEBSITE-BALANCE-10DEC09 12 12/10WEBSITE-BAL |
|
6
|
-
2006-12-24 | $0.23 | DEBIT; WEBSITE-BALANCE-17DEC09 12 12/17WEBSITE-BAL |
|
7
|
-
2007-12-24 | $1,558.52 | CREDIT; Blarg BLARG REVENUE PPD ID: 00jah78563 |
|
8
|
-
2008-12-24 | $3,520.00 | CREDIT; Some Company vendorpymt PPD ID: 59728JSL20 |
|
9
|
-
2009-12-24 | -$7.00 | DEBIT; GITHUB 041287430274 CA 12/22GITHUB 04 |
|
10
|
-
2010-12-24 | -$20.00 | CHECK; CHECK 2656 |
|
11
|
-
2011-12-24 | -$85.00 | DEBIT; HOST 037196321563 MO 12/22SLICEHOST |
|
2
|
+
Date | Amount | Description |
|
3
|
+
2003-12-24 | $2,105.00 | CREDIT; Some Company vendorpymt PPD ID: 5KL3832735 |
|
4
|
+
2004-12-24 | -$116.22 | CREDIT; PAYPAL TRANSFER PPD ID: PAYPALSDSL |
|
5
|
+
2005-12-24 | -$0.96 | DEBIT; WEBSITE-BALANCE-10DEC09 12 12/10WEBSITE-BAL |
|
6
|
+
2006-12-24 | $0.23 | DEBIT; WEBSITE-BALANCE-17DEC09 12 12/17WEBSITE-BAL |
|
7
|
+
2007-12-24 | $1,558.52 | CREDIT; Blarg BLARG REVENUE PPD ID: 00jah78563 |
|
8
|
+
2008-12-24 | $3,520.00 | CREDIT; Some Company vendorpymt PPD ID: 59728JSL20 |
|
9
|
+
2009-12-24 | -$7.00 | DEBIT; GITHUB 041287430274 CA 12/22GITHUB 04 |
|
10
|
+
2010-12-24 | -$20.00 | CHECK; CHECK 2656 |
|
11
|
+
2011-12-24 | -$85.00 | DEBIT; HOST 037196321563 MO 12/22SLICEHOST |
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reckon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Cantino
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2024-
|
13
|
+
date: 2024-11-27 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rspec
|