baa_chan 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'baa_chan'
5
+ puts BaaChan::Reader.new(ARGV[0]).call.to_builder
data/lib/baa_chan.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'baa_chan/reader'
@@ -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: []