baa_chan 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/baa_chan +5 -0
- data/lib/baa_chan.rb +3 -0
- data/lib/baa_chan/costs.rb +18 -0
- data/lib/baa_chan/layout.rb +31 -0
- data/lib/baa_chan/layouts/clear.yml +30 -0
- data/lib/baa_chan/layouts/genial.yml +36 -0
- data/lib/baa_chan/layouts/singulare.yml +36 -0
- data/lib/baa_chan/parser.rb +136 -0
- data/lib/baa_chan/pdf_to_text.rb +21 -0
- data/lib/baa_chan/reader.rb +55 -0
- data/lib/baa_chan/trade.rb +14 -0
- data/lib/baa_chan/trade_confirmation.rb +53 -0
- metadata +100 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6dd4992c1495048ce9123656452c1d9e2ed8ce3dffd2c7f070172c958f748cca
|
4
|
+
data.tar.gz: b6a55877a52d39e01aeb3bb6f11d54a6f7f9a86e5622b70ba4e82e13e1302701
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 392c751ceccd693dfb2a85d6140b69c8d1bfea4f08088917f8de8e0965deef3234b839aaba431a35ea996041cf0c4a50932b3353039e184cfda1a3b2fd178869
|
7
|
+
data.tar.gz: 62c4a801ea57f5ff746a3edec50458ae18ad4e3ca8d01330bbdbf259ebe3d4eb3dd7132b4e46e654807558c6fe2aafdf77543c7f1d768c9f3d9c7524c96dc5fb
|
data/bin/baa_chan
ADDED
data/lib/baa_chan.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BaaChan
|
4
|
+
class Costs
|
5
|
+
attr_accessor :clearing_fee, :registration_fee, :emoluments,
|
6
|
+
:brokerage, :iss, :irrf, :pis_cofins
|
7
|
+
|
8
|
+
def initialize(brokerage, clearing_fee, registration_fee, emoluments, options)
|
9
|
+
@brokerage = brokerage
|
10
|
+
@clearing_fee = clearing_fee
|
11
|
+
@registration_fee = registration_fee
|
12
|
+
@emoluments = emoluments
|
13
|
+
@iss = options[:iss] || 0.0
|
14
|
+
@irrf = options[:irrf] || 0.0
|
15
|
+
@pis_cofins = options[:pis_cofins] || 0.0
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Layout
|
4
|
+
LAYOUTS_PATH = 'lib/baa_chan/layouts'
|
5
|
+
|
6
|
+
def initialize(broker)
|
7
|
+
@broker = broker
|
8
|
+
end
|
9
|
+
|
10
|
+
def attributes
|
11
|
+
@attributes ||= YAML.safe_load(File.read(File.join(LAYOUTS_PATH, "#{@broker}.yml")))
|
12
|
+
end
|
13
|
+
|
14
|
+
def line
|
15
|
+
attributes[caller_locations.first.label]['line'].to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
def index(attr_name = nil)
|
19
|
+
attr = attr_name || caller_locations.first.label
|
20
|
+
|
21
|
+
attributes[attr]['index'].to_i
|
22
|
+
end
|
23
|
+
|
24
|
+
def trade_prefix
|
25
|
+
attributes['trades']['prefix']
|
26
|
+
end
|
27
|
+
|
28
|
+
def regexp_for(attr)
|
29
|
+
attributes[attr]['regexp']
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
broker:
|
2
|
+
line: 4
|
3
|
+
trade_confirmation_number:
|
4
|
+
line: 2
|
5
|
+
index: 0
|
6
|
+
trade_date:
|
7
|
+
line: 2
|
8
|
+
index: 2
|
9
|
+
trades:
|
10
|
+
prefix: 1-BOVESPA
|
11
|
+
operation:
|
12
|
+
index: 1
|
13
|
+
ticker:
|
14
|
+
index: 3
|
15
|
+
quantity:
|
16
|
+
index: 7
|
17
|
+
price:
|
18
|
+
index: 8
|
19
|
+
clearing_fee:
|
20
|
+
regexp: .*Taxa de liquidação.*
|
21
|
+
index: 7
|
22
|
+
registration_fee:
|
23
|
+
regexp: .*Taxa de Registro.*
|
24
|
+
index: 7
|
25
|
+
emoluments:
|
26
|
+
regexp: .*Emolumentos.*
|
27
|
+
index: 1
|
28
|
+
irrf:
|
29
|
+
regexp: .*I\.R\.R\.F.*
|
30
|
+
index: 5
|
@@ -0,0 +1,36 @@
|
|
1
|
+
broker:
|
2
|
+
line: 4
|
3
|
+
trade_confirmation_number:
|
4
|
+
line: 2
|
5
|
+
index: 0
|
6
|
+
trade_date:
|
7
|
+
line: 2
|
8
|
+
index: 2
|
9
|
+
trades:
|
10
|
+
prefix: 1-BOVESPA
|
11
|
+
operation:
|
12
|
+
index: 1
|
13
|
+
ticker:
|
14
|
+
index: 3
|
15
|
+
quantity:
|
16
|
+
index: 6
|
17
|
+
price:
|
18
|
+
index: 7
|
19
|
+
clearing_fee:
|
20
|
+
regexp: .*Taxa de liquidação.*
|
21
|
+
index: 7
|
22
|
+
registration_fee:
|
23
|
+
regexp: .*Taxa de Registro.*
|
24
|
+
index: 7
|
25
|
+
emoluments:
|
26
|
+
regexp: .*Emolumentos.*
|
27
|
+
index: 1
|
28
|
+
brokerage:
|
29
|
+
regexp: ^A coluna Q.*
|
30
|
+
index: 9
|
31
|
+
iss:
|
32
|
+
regexp: ^ISS \( SÃO PAULO \).*
|
33
|
+
index: 5
|
34
|
+
pis_cofins:
|
35
|
+
regexp: ^ISS/PIS/COFINS.*
|
36
|
+
index: 1
|
@@ -0,0 +1,36 @@
|
|
1
|
+
broker:
|
2
|
+
line: 3
|
3
|
+
trade_confirmation_number:
|
4
|
+
line: 2
|
5
|
+
index: 0
|
6
|
+
trade_date:
|
7
|
+
line: 2
|
8
|
+
index: 2
|
9
|
+
trades:
|
10
|
+
prefix: BM&FBOVESPA S/A.
|
11
|
+
operation:
|
12
|
+
index: 2
|
13
|
+
ticker:
|
14
|
+
index: 6
|
15
|
+
quantity:
|
16
|
+
index: 9
|
17
|
+
price:
|
18
|
+
index: 10
|
19
|
+
clearing_fee:
|
20
|
+
regexp: .*Taxa de Liquidação.*
|
21
|
+
index: 7
|
22
|
+
registration_fee:
|
23
|
+
regexp: .*Taxa de Registro.*
|
24
|
+
index: 7
|
25
|
+
emoluments:
|
26
|
+
regexp: .*Emolumentos.*
|
27
|
+
index: 5
|
28
|
+
brokerage:
|
29
|
+
regexp: ^Corretagem.*
|
30
|
+
index: 1
|
31
|
+
iss:
|
32
|
+
regexp: ^ISS \(SAO PAULO\).*
|
33
|
+
index: 3
|
34
|
+
irrf:
|
35
|
+
regexp: .*I\.R\.R\.F.*
|
36
|
+
index: 14
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'baa_chan/trade_confirmation'
|
4
|
+
require 'baa_chan/trade'
|
5
|
+
require 'baa_chan/costs'
|
6
|
+
require 'date'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
module BaaChan
|
10
|
+
class CostsParserError < StandardError; end
|
11
|
+
|
12
|
+
class Parser
|
13
|
+
def initialize(lines, layout)
|
14
|
+
@lines = lines
|
15
|
+
@layout = layout
|
16
|
+
end
|
17
|
+
|
18
|
+
def call
|
19
|
+
TradeConfirmation.new(broker, trade_confirmation_number, trade_date, trades, costs)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def broker
|
25
|
+
@broker ||= @lines[@layout.line]
|
26
|
+
end
|
27
|
+
|
28
|
+
def trade_confirmation_number
|
29
|
+
@trade_confirmation_number ||= @lines[@layout.line].split[@layout.index]
|
30
|
+
end
|
31
|
+
|
32
|
+
def trade_date
|
33
|
+
@trade_date ||= Date.parse @lines[@layout.line].split[@layout.index]
|
34
|
+
end
|
35
|
+
|
36
|
+
def trades
|
37
|
+
@trades ||= TradeParser.new(@lines, @layout).parse
|
38
|
+
end
|
39
|
+
|
40
|
+
def costs
|
41
|
+
@costs ||= CostsParser.new(@lines, @layout).parse
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class TradeParser
|
46
|
+
def initialize(lines, layout)
|
47
|
+
@lines = lines
|
48
|
+
@layout = layout
|
49
|
+
end
|
50
|
+
|
51
|
+
def parse
|
52
|
+
@lines.each_with_object([]) do |line, trades|
|
53
|
+
next unless line.include? @layout.trade_prefix
|
54
|
+
|
55
|
+
@trade_line = line
|
56
|
+
trades << Trade.new(operation, ticker, quantity, price)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def operation
|
63
|
+
@trade_line.split[@layout.index] == 'V' ? 'Sell' : 'Buy'
|
64
|
+
end
|
65
|
+
|
66
|
+
def ticker
|
67
|
+
@trade_line.split[@layout.index]
|
68
|
+
end
|
69
|
+
|
70
|
+
def quantity
|
71
|
+
@trade_line.split[@layout.index].to_i
|
72
|
+
end
|
73
|
+
|
74
|
+
def price
|
75
|
+
@trade_line.split[@layout.index].gsub(',', '.').to_f
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class CostsParser
|
80
|
+
def initialize(lines, layout)
|
81
|
+
@lines = lines
|
82
|
+
@layout = layout
|
83
|
+
end
|
84
|
+
|
85
|
+
def parse
|
86
|
+
Costs.new(brokerage, clearing_fee, registration_fee, emoluments, { iss: iss, irrf: irrf, pis_cofins: pis_cofins })
|
87
|
+
rescue StandardError => e
|
88
|
+
raise CostsParserError, e.message
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def value_for(attr_name)
|
94
|
+
@lines.find { |line| line.match? @layout.regexp_for(attr_name) }
|
95
|
+
.split[@layout.index(attr_name)]
|
96
|
+
.gsub(',', '.')
|
97
|
+
.to_f
|
98
|
+
end
|
99
|
+
|
100
|
+
def brokerage
|
101
|
+
value_for('brokerage')
|
102
|
+
rescue NoMethodError
|
103
|
+
0.0
|
104
|
+
end
|
105
|
+
|
106
|
+
def clearing_fee
|
107
|
+
value_for('clearing_fee')
|
108
|
+
end
|
109
|
+
|
110
|
+
def registration_fee
|
111
|
+
value_for('registration_fee')
|
112
|
+
end
|
113
|
+
|
114
|
+
def emoluments
|
115
|
+
value_for('emoluments')
|
116
|
+
end
|
117
|
+
|
118
|
+
def iss
|
119
|
+
value_for('iss')
|
120
|
+
rescue NoMethodError
|
121
|
+
0.0
|
122
|
+
end
|
123
|
+
|
124
|
+
def irrf
|
125
|
+
value_for('irrf')
|
126
|
+
rescue NoMethodError
|
127
|
+
0.0
|
128
|
+
end
|
129
|
+
|
130
|
+
def pis_cofins
|
131
|
+
value_for('pis_cofins')
|
132
|
+
rescue NoMethodError
|
133
|
+
0.0
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'English'
|
4
|
+
|
5
|
+
module BaaChan
|
6
|
+
class UnreadablePDFError < StandardError; end
|
7
|
+
|
8
|
+
class InvalidExtensionError < StandardError; end
|
9
|
+
|
10
|
+
class PdfToText
|
11
|
+
def self.call(source)
|
12
|
+
raise InvalidExtensionError unless source.match /\.pdf$/
|
13
|
+
|
14
|
+
content = `pdftotext -layout #{source} -`
|
15
|
+
|
16
|
+
raise UnreadablePDFError unless $CHILD_STATUS.success?
|
17
|
+
|
18
|
+
content
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BaaChan
|
4
|
+
class MalformedPDFError < StandardError; end
|
5
|
+
|
6
|
+
class UnknownLayoutError < StandardError; end
|
7
|
+
|
8
|
+
class Reader
|
9
|
+
BROKER_LIST = {
|
10
|
+
singulare: 'Singulare - corretora de titulos de valores mobiliarios',
|
11
|
+
genial: 'GENIAL INVESTIMENTOS CORRETORA DE VALORES MOBILIÁRIOS S.A.',
|
12
|
+
clear: 'CLEAR CORRETORA - GRUPO XP'
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def initialize(source)
|
16
|
+
@source = source
|
17
|
+
end
|
18
|
+
|
19
|
+
def call
|
20
|
+
parse(PdfToText.call(@source))
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def parse(text)
|
26
|
+
lines = sanitize(text)
|
27
|
+
|
28
|
+
layout = Layout.new(detect_layout(lines))
|
29
|
+
|
30
|
+
Parser.new(lines, layout).call
|
31
|
+
end
|
32
|
+
|
33
|
+
def sanitize(content)
|
34
|
+
content.split("\n").map(&:strip)
|
35
|
+
end
|
36
|
+
|
37
|
+
def detect_layout(lines)
|
38
|
+
broker = if lines[3] == BROKER_LIST[:singulare]
|
39
|
+
'singulare'
|
40
|
+
elsif lines[4] == BROKER_LIST[:genial]
|
41
|
+
'genial'
|
42
|
+
elsif lines[4] == BROKER_LIST[:clear]
|
43
|
+
'clear'
|
44
|
+
else
|
45
|
+
raise UnknownLayoutError
|
46
|
+
end
|
47
|
+
|
48
|
+
return broker
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
require 'baa_chan/pdf_to_text'
|
54
|
+
require 'baa_chan/parser'
|
55
|
+
require 'baa_chan/layout'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BaaChan
|
4
|
+
class Trade
|
5
|
+
attr_accessor :operation, :ticker, :quantity, :price
|
6
|
+
|
7
|
+
def initialize(operation, ticker, quantity, price)
|
8
|
+
@operation = operation
|
9
|
+
@ticker = ticker
|
10
|
+
@quantity = quantity
|
11
|
+
@price = price
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jbuilder'
|
4
|
+
|
5
|
+
module BaaChan
|
6
|
+
class TradeConfirmation
|
7
|
+
attr_accessor :broker, :trade_confirmation_number, :trade_date, :trades, :costs
|
8
|
+
|
9
|
+
def initialize(broker, trade_confirmation_number, trade_date, trades, costs)
|
10
|
+
@broker = broker
|
11
|
+
@trade_confirmation_number = trade_confirmation_number
|
12
|
+
@trade_date = trade_date
|
13
|
+
@trades = trades
|
14
|
+
@costs = costs
|
15
|
+
end
|
16
|
+
|
17
|
+
def total_cost
|
18
|
+
@costs.brokerage +
|
19
|
+
@costs.registration_fee +
|
20
|
+
@costs.emoluments +
|
21
|
+
@costs.pis_cofins +
|
22
|
+
@costs.clearing_fee
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_builder
|
26
|
+
Jbuilder.encode do |json|
|
27
|
+
json.call(
|
28
|
+
self,
|
29
|
+
:broker,
|
30
|
+
:trade_confirmation_number,
|
31
|
+
:trade_date
|
32
|
+
)
|
33
|
+
|
34
|
+
json.trades @trades do |trade|
|
35
|
+
json.operation trade.operation
|
36
|
+
json.ticker trade.ticker
|
37
|
+
json.quantity trade.quantity
|
38
|
+
json.price trade.price
|
39
|
+
end
|
40
|
+
|
41
|
+
json.costs do
|
42
|
+
json.brokerage @costs.brokerage
|
43
|
+
json.clearing_fee @costs.clearing_fee
|
44
|
+
json.registration_fee @costs.registration_fee
|
45
|
+
json.emoluments @costs.emoluments
|
46
|
+
json.iss @costs.iss if @costs.iss
|
47
|
+
json.irrf @costs.irrf if @costs.irrf
|
48
|
+
json.pis_cofins @costs.pis_cofins if @costs.pis_cofins
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: baa_chan
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Diogo Noda
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-04-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pry-byebug
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.9.0
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.9.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.10.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.10.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: jbuilder
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.11.2
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.11.2
|
55
|
+
description: |
|
56
|
+
BaaChan will read your trade confirmations and provide you
|
57
|
+
details about your trades helping you out handling your
|
58
|
+
finances
|
59
|
+
email: diogotnoda@gmail.com
|
60
|
+
executables:
|
61
|
+
- baa_chan
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- bin/baa_chan
|
66
|
+
- lib/baa_chan.rb
|
67
|
+
- lib/baa_chan/costs.rb
|
68
|
+
- lib/baa_chan/layout.rb
|
69
|
+
- lib/baa_chan/layouts/clear.yml
|
70
|
+
- lib/baa_chan/layouts/genial.yml
|
71
|
+
- lib/baa_chan/layouts/singulare.yml
|
72
|
+
- lib/baa_chan/parser.rb
|
73
|
+
- lib/baa_chan/pdf_to_text.rb
|
74
|
+
- lib/baa_chan/reader.rb
|
75
|
+
- lib/baa_chan/trade.rb
|
76
|
+
- lib/baa_chan/trade_confirmation.rb
|
77
|
+
homepage: https://rubygems.org/gems/baa_chan
|
78
|
+
licenses:
|
79
|
+
- MIT
|
80
|
+
metadata: {}
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 3.0.0
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
requirements: []
|
96
|
+
rubygems_version: 3.2.3
|
97
|
+
signing_key:
|
98
|
+
specification_version: 4
|
99
|
+
summary: Trade Confirmation Reader
|
100
|
+
test_files: []
|