mindee 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitattributes +14 -0
- data/.gitignore +69 -0
- data/.rubocop.yml +41 -0
- data/CHANGELOG.md +4 -0
- data/CODE_OF_CONDUCT.md +129 -0
- data/Gemfile +13 -0
- data/LICENSE +21 -0
- data/README.md +66 -0
- data/Rakefile +5 -0
- data/bin/console +14 -0
- data/bin/mindee.rb +137 -0
- data/lib/mindee/client.rb +171 -0
- data/lib/mindee/document_config.rb +200 -0
- data/lib/mindee/documents/base.rb +35 -0
- data/lib/mindee/documents/custom.rb +44 -0
- data/lib/mindee/documents/financial_doc.rb +135 -0
- data/lib/mindee/documents/invoice.rb +162 -0
- data/lib/mindee/documents/passport.rb +163 -0
- data/lib/mindee/documents/receipt.rb +109 -0
- data/lib/mindee/documents.rb +7 -0
- data/lib/mindee/endpoint.rb +105 -0
- data/lib/mindee/fields/amount.rb +17 -0
- data/lib/mindee/fields/base.rb +61 -0
- data/lib/mindee/fields/company_registration.rb +17 -0
- data/lib/mindee/fields/datefield.rb +30 -0
- data/lib/mindee/fields/list_field.rb +71 -0
- data/lib/mindee/fields/locale.rb +45 -0
- data/lib/mindee/fields/orientation.rb +26 -0
- data/lib/mindee/fields/payment_details.rb +33 -0
- data/lib/mindee/fields/tax.rb +35 -0
- data/lib/mindee/fields.rb +11 -0
- data/lib/mindee/geometry.rb +21 -0
- data/lib/mindee/inputs.rb +153 -0
- data/lib/mindee/response.rb +27 -0
- data/lib/mindee/version.rb +21 -0
- data/lib/mindee.rb +8 -0
- data/mindee.gemspec +34 -0
- metadata +128 -0
@@ -0,0 +1,162 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../fields'
|
4
|
+
require_relative 'base'
|
5
|
+
|
6
|
+
module Mindee
|
7
|
+
# Invoice document.
|
8
|
+
class Invoice < Document
|
9
|
+
# @return [Mindee::Locale]
|
10
|
+
attr_reader :locale
|
11
|
+
# @return [Mindee::Amount]
|
12
|
+
attr_reader :total_incl
|
13
|
+
# @return [Mindee::Amount]
|
14
|
+
attr_reader :total_excl
|
15
|
+
# @return [Mindee::Amount]
|
16
|
+
attr_reader :total_tax
|
17
|
+
# @return [Mindee::DateField]
|
18
|
+
attr_reader :date
|
19
|
+
# @return [Mindee::Field]
|
20
|
+
attr_reader :invoice_number
|
21
|
+
# @return [Mindee::DateField]
|
22
|
+
attr_reader :due_date
|
23
|
+
# @return [Array<Mindee::TaxField>]
|
24
|
+
attr_reader :taxes
|
25
|
+
# @return [Array<Mindee::CompanyRegistration>]
|
26
|
+
attr_reader :customer_company_registration
|
27
|
+
# @return [Array<Mindee::PaymentDetails>]
|
28
|
+
attr_reader :payment_details
|
29
|
+
# @return [Array<Mindee::CompanyRegistration>]
|
30
|
+
attr_reader :company_registration
|
31
|
+
# @return [Mindee::Field]
|
32
|
+
attr_reader :customer_name
|
33
|
+
# @return [Mindee::Field]
|
34
|
+
attr_reader :supplier
|
35
|
+
# @return [Mindee::Field]
|
36
|
+
attr_reader :supplier_address
|
37
|
+
# @return [Mindee::Field]
|
38
|
+
attr_reader :customer_address
|
39
|
+
# @return [Mindee::Orientation]
|
40
|
+
attr_reader :orientation
|
41
|
+
|
42
|
+
# @param prediction [Hash]
|
43
|
+
# @param input_file [Mindee::InputDocument, nil]
|
44
|
+
# @param page_id [Integer, nil]
|
45
|
+
def initialize(prediction, input_file: nil, page_id: nil)
|
46
|
+
super('invoice', input_file: input_file)
|
47
|
+
@orientation = Orientation.new(prediction['orientation'], page_id) if page_id
|
48
|
+
@locale = Locale.new(prediction['locale'])
|
49
|
+
@total_incl = Amount.new(prediction['total_incl'], page_id)
|
50
|
+
@total_excl = Amount.new(prediction['total_excl'], page_id)
|
51
|
+
@customer_address = Field.new(prediction['customer_address'], page_id)
|
52
|
+
@customer_name = Field.new(prediction['customer'], page_id)
|
53
|
+
@date = DateField.new(prediction['date'], page_id)
|
54
|
+
@due_date = DateField.new(prediction['due_date'], page_id)
|
55
|
+
@invoice_number = Field.new(prediction['invoice_number'], page_id)
|
56
|
+
@supplier = Field.new(prediction['supplier'], page_id)
|
57
|
+
@supplier_address = Field.new(prediction['supplier_address'], page_id)
|
58
|
+
|
59
|
+
@customer_company_registration = []
|
60
|
+
prediction['customer_company_registration'].each do |item|
|
61
|
+
@customer_company_registration.push(CompanyRegistration.new(item, page_id))
|
62
|
+
end
|
63
|
+
@taxes = []
|
64
|
+
prediction['taxes'].each do |item|
|
65
|
+
@taxes.push(TaxField.new(item, page_id))
|
66
|
+
end
|
67
|
+
@payment_details = []
|
68
|
+
prediction['payment_details'].each do |item|
|
69
|
+
@payment_details.push(PaymentDetails.new(item, page_id))
|
70
|
+
end
|
71
|
+
@company_registration = []
|
72
|
+
prediction['company_registration'].each do |item|
|
73
|
+
@company_registration.push(CompanyRegistration.new(item, page_id))
|
74
|
+
end
|
75
|
+
|
76
|
+
@total_tax = Amount.new(
|
77
|
+
{ value: nil, confidence: 0.0 }, page_id
|
78
|
+
)
|
79
|
+
reconstruct(page_id)
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_s
|
83
|
+
customer_company_registration = @customer_company_registration.map(&:value).join('; ')
|
84
|
+
payment_details = @payment_details.map(&:to_s).join("\n ")
|
85
|
+
company_registration = @company_registration.map(&:to_s).join('; ')
|
86
|
+
taxes = @taxes.join("\n ")
|
87
|
+
out_str = String.new
|
88
|
+
out_str << '-----Invoice data-----'
|
89
|
+
out_str << "\nFilename: #{@filename}".rstrip
|
90
|
+
out_str << "\nInvoice number: #{@invoice_number}".rstrip
|
91
|
+
out_str << "\nTotal amount including taxes: #{@total_incl}".rstrip
|
92
|
+
out_str << "\nTotal amount excluding taxes: #{@total_excl}".rstrip
|
93
|
+
out_str << "\nInvoice date: #{@date}".rstrip
|
94
|
+
out_str << "\nInvoice due date: #{@due_date}".rstrip
|
95
|
+
out_str << "\nSupplier name: #{@supplier}".rstrip
|
96
|
+
out_str << "\nSupplier address: #{@supplier_address}".rstrip
|
97
|
+
out_str << "\nCustomer name: #{@customer_name}".rstrip
|
98
|
+
out_str << "\nCustomer company registration: #{customer_company_registration}".rstrip
|
99
|
+
out_str << "\nCustomer address: #{@customer_address}".rstrip
|
100
|
+
out_str << "\nPayment details: #{payment_details}".rstrip
|
101
|
+
out_str << "\nCompany numbers: #{company_registration}".rstrip
|
102
|
+
out_str << "\nTaxes: #{taxes}".rstrip
|
103
|
+
out_str << "\nTotal taxes: #{@total_tax}".rstrip
|
104
|
+
out_str << "\nLocale: #{@locale}".rstrip
|
105
|
+
out_str << "\n----------------------"
|
106
|
+
out_str
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def reconstruct(page_id)
|
112
|
+
construct_total_tax_from_taxes(page_id)
|
113
|
+
construct_total_excl_from_tcc_and_taxes(page_id)
|
114
|
+
construct_total_incl_from_taxes_plus_excl(page_id)
|
115
|
+
construct_total_tax_from_totals(page_id)
|
116
|
+
end
|
117
|
+
|
118
|
+
def construct_total_excl_from_tcc_and_taxes(page_id)
|
119
|
+
return if @total_incl.value.nil? || taxes.empty? || !@total_excl.value.nil?
|
120
|
+
|
121
|
+
total_excl = {
|
122
|
+
'value' => @total_incl.value - @taxes.map(&:value).sum,
|
123
|
+
'confidence' => Field.array_confidence(@taxes) * @total_incl.confidence,
|
124
|
+
}
|
125
|
+
@total_excl = Amount.new(total_excl, page_id, reconstructed: true)
|
126
|
+
end
|
127
|
+
|
128
|
+
def construct_total_incl_from_taxes_plus_excl(page_id)
|
129
|
+
return if @total_excl.value.nil? || @taxes.empty? || !@total_incl.value.nil?
|
130
|
+
|
131
|
+
total_incl = {
|
132
|
+
'value' => @taxes.map(&:value).sum + @total_excl.value,
|
133
|
+
'confidence' => Field.array_confidence(@taxes) * @total_excl.confidence,
|
134
|
+
}
|
135
|
+
@total_incl = Amount.new(total_incl, page_id, reconstructed: true)
|
136
|
+
end
|
137
|
+
|
138
|
+
def construct_total_tax_from_taxes(page_id)
|
139
|
+
return if @taxes.empty?
|
140
|
+
|
141
|
+
total_tax = {
|
142
|
+
'value' => @taxes.map(&:value).sum,
|
143
|
+
'confidence' => Field.array_confidence(@taxes),
|
144
|
+
}
|
145
|
+
return unless total_tax['value'].positive?
|
146
|
+
|
147
|
+
@total_tax = Amount.new(total_tax, page_id, reconstructed: true)
|
148
|
+
end
|
149
|
+
|
150
|
+
def construct_total_tax_from_totals(page_id)
|
151
|
+
return if !@total_tax.value.nil? || @total_incl.value.nil? || @total_excl.value.nil?
|
152
|
+
|
153
|
+
total_tax = {
|
154
|
+
'value' => @total_incl.value - @total_excl.value,
|
155
|
+
'confidence' => Field.array_confidence(@taxes),
|
156
|
+
}
|
157
|
+
return unless total_tax['value'] >= 0
|
158
|
+
|
159
|
+
@total_tax = Amount.new(total_tax, page_id, reconstructed: true)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mrz'
|
4
|
+
|
5
|
+
require_relative '../fields'
|
6
|
+
require_relative 'base'
|
7
|
+
|
8
|
+
# We need to do this disgusting thing to avoid the following error message:
|
9
|
+
# td3 line one does not match the required format (MRZ::InvalidFormatError)
|
10
|
+
#
|
11
|
+
# See:
|
12
|
+
# https://github.com/streetspotr/mrz/issues/2
|
13
|
+
# https://github.com/streetspotr/mrz/pull/3
|
14
|
+
#
|
15
|
+
MRZ::TD3Parser::FORMAT_ONE = %r{\A(.{2})(.{3})([^<]+)<(.*)\z}.freeze
|
16
|
+
|
17
|
+
module Mindee
|
18
|
+
# Passport document.
|
19
|
+
class Passport < Document
|
20
|
+
# @return [Mindee::Orientation]
|
21
|
+
attr_reader :orientation
|
22
|
+
attr_reader :country,
|
23
|
+
:id_number,
|
24
|
+
:expiry_date,
|
25
|
+
:issuance_date,
|
26
|
+
:surname,
|
27
|
+
:given_names,
|
28
|
+
:full_name,
|
29
|
+
:birth_date,
|
30
|
+
:birth_place,
|
31
|
+
:gender,
|
32
|
+
:mrz1,
|
33
|
+
:mrz2,
|
34
|
+
:mrz
|
35
|
+
|
36
|
+
# @param prediction [Hash]
|
37
|
+
# @param input_file [Mindee::InputDocument, nil]
|
38
|
+
# @param page_id [Integer, nil]
|
39
|
+
def initialize(prediction, input_file: nil, page_id: nil)
|
40
|
+
super('passport', input_file: input_file)
|
41
|
+
@orientation = Orientation.new(prediction['orientation'], page_id) if page_id
|
42
|
+
@country = Field.new(prediction['country'], page_id)
|
43
|
+
@id_number = Field.new(prediction['id_number'], page_id)
|
44
|
+
@birth_date = DateField.new(prediction['birth_date'], page_id)
|
45
|
+
@expiry_date = DateField.new(prediction['expiry_date'], page_id)
|
46
|
+
@issuance_date = DateField.new(prediction['issuance_date'], page_id)
|
47
|
+
@birth_place = Field.new(prediction['birth_place'], page_id)
|
48
|
+
@gender = Field.new(prediction['gender'], page_id)
|
49
|
+
@surname = Field.new(prediction['surname'], page_id)
|
50
|
+
@mrz1 = Field.new(prediction['mrz1'], page_id)
|
51
|
+
@mrz2 = Field.new(prediction['mrz2'], page_id)
|
52
|
+
@given_names = []
|
53
|
+
prediction['given_names'].each do |item|
|
54
|
+
@given_names.push(Field.new(item, page_id))
|
55
|
+
end
|
56
|
+
@full_name = construct_full_name(page_id)
|
57
|
+
@mrz = construct_mrz(page_id)
|
58
|
+
check_mrz
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_s
|
62
|
+
given_names = @given_names.join(' ')
|
63
|
+
out_str = String.new
|
64
|
+
out_str << '-----Passport data-----'
|
65
|
+
out_str << "\nFilename: #{@filename}".rstrip
|
66
|
+
out_str << "\nFull name: #{@full_name}".rstrip
|
67
|
+
out_str << "\nGiven names: #{given_names}".rstrip
|
68
|
+
out_str << "\nSurname: #{@surname}".rstrip
|
69
|
+
out_str << "\nCountry: #{@country}".rstrip
|
70
|
+
out_str << "\nID Number: #{@id_number}".rstrip
|
71
|
+
out_str << "\nIssuance date: #{@issuance_date}".rstrip
|
72
|
+
out_str << "\nBirth date: #{@birth_date}".rstrip
|
73
|
+
out_str << "\nExpiry date: #{@expiry_date}".rstrip
|
74
|
+
out_str << "\nMRZ 1: #{@mrz1}".rstrip
|
75
|
+
out_str << "\nMRZ 2: #{@mrz2}".rstrip
|
76
|
+
out_str << "\nMRZ: #{@mrz}".rstrip
|
77
|
+
out_str << "\n----------------------"
|
78
|
+
out_str
|
79
|
+
end
|
80
|
+
|
81
|
+
# @return [Boolean]
|
82
|
+
def expired?
|
83
|
+
return true unless @expiry_date.date_object
|
84
|
+
|
85
|
+
@expiry_date.date_object < Date.today
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def check_mrz
|
91
|
+
return if @mrz1.value.nil? || @mrz2.value.nil?
|
92
|
+
|
93
|
+
mrz = MRZ.parse([@mrz1.value, @mrz2.value])
|
94
|
+
checks = {
|
95
|
+
mrz_valid: valid_mrz?(mrz),
|
96
|
+
mrz_valid_birth_date: valid_birth_date?(mrz),
|
97
|
+
mrz_valid_expiry_date: valid_expiry_date?(mrz),
|
98
|
+
mrz_valid_id_number: valid_id_number?(mrz),
|
99
|
+
mrz_valid_surname: valid_surname?(mrz),
|
100
|
+
mrz_valid_country: valid_country?(mrz),
|
101
|
+
}
|
102
|
+
@checklist.merge!(checks)
|
103
|
+
end
|
104
|
+
|
105
|
+
def valid_mrz?(mrz)
|
106
|
+
check = mrz.valid?
|
107
|
+
@mrz.confidence = 1.0 if check
|
108
|
+
check
|
109
|
+
end
|
110
|
+
|
111
|
+
def valid_birth_date?(mrz)
|
112
|
+
check = mrz.valid_birth_date? && mrz.birth_date == @birth_date.date_object
|
113
|
+
@birth_date.confidence = 1.0 if check
|
114
|
+
check
|
115
|
+
end
|
116
|
+
|
117
|
+
def valid_expiry_date?(mrz)
|
118
|
+
check = mrz.valid_expiration_date? && mrz.expiration_date == @expiry_date.date_object
|
119
|
+
@expiry_date.confidence = 1.0 if check
|
120
|
+
check
|
121
|
+
end
|
122
|
+
|
123
|
+
def valid_id_number?(mrz)
|
124
|
+
check = mrz.valid_document_number? && mrz.document_number == @id_number.value
|
125
|
+
@id_number.confidence = 1.0 if check
|
126
|
+
check
|
127
|
+
end
|
128
|
+
|
129
|
+
def valid_surname?(mrz)
|
130
|
+
check = mrz.last_name == @surname.value
|
131
|
+
@surname.confidence = 1.0 if check
|
132
|
+
check
|
133
|
+
end
|
134
|
+
|
135
|
+
def valid_country?(mrz)
|
136
|
+
check = mrz.nationality == @country.value
|
137
|
+
@country.confidence = 1.0 if check
|
138
|
+
check
|
139
|
+
end
|
140
|
+
|
141
|
+
def construct_full_name(page_id)
|
142
|
+
return unless @surname.value &&
|
143
|
+
!@given_names.empty? &&
|
144
|
+
@given_names[0].value
|
145
|
+
|
146
|
+
full_name = {
|
147
|
+
'value' => "#{@given_names[0].value} #{@surname.value}",
|
148
|
+
'confidence' => Field.array_confidence([@surname, @given_names[0]]),
|
149
|
+
}
|
150
|
+
Field.new(full_name, page_id, reconstructed: true)
|
151
|
+
end
|
152
|
+
|
153
|
+
def construct_mrz(page_id)
|
154
|
+
return if @mrz1.value.nil? || @mrz2.value.nil?
|
155
|
+
|
156
|
+
mrz = {
|
157
|
+
'value' => @mrz1.value + @mrz2.value,
|
158
|
+
'confidence' => Field.array_confidence([@mrz1, @mrz2]),
|
159
|
+
}
|
160
|
+
Field.new(mrz, page_id, reconstructed: true)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../fields'
|
4
|
+
require_relative 'base'
|
5
|
+
|
6
|
+
module Mindee
|
7
|
+
# Receipt document.
|
8
|
+
class Receipt < Document
|
9
|
+
# @return [Mindee::Locale]
|
10
|
+
attr_reader :locale
|
11
|
+
# @return [Mindee::Amount]
|
12
|
+
attr_reader :total_incl
|
13
|
+
# @return [Mindee::Amount]
|
14
|
+
attr_reader :total_excl
|
15
|
+
# @return [Mindee::Amount]
|
16
|
+
attr_reader :total_tax
|
17
|
+
# @return [Mindee::DateField]
|
18
|
+
attr_reader :date
|
19
|
+
# @return [Mindee::Field]
|
20
|
+
attr_reader :supplier
|
21
|
+
# @return [Array<Mindee::TaxField>]
|
22
|
+
attr_reader :taxes
|
23
|
+
# @return [Mindee::Orientation]
|
24
|
+
attr_reader :orientation
|
25
|
+
# @return [Mindee::Field]
|
26
|
+
attr_reader :time
|
27
|
+
# @return [Mindee::Field]
|
28
|
+
attr_reader :category
|
29
|
+
|
30
|
+
# @param prediction [Hash]
|
31
|
+
# @param input_file [Mindee::InputDocument, nil]
|
32
|
+
# @param page_id [Integer, nil]
|
33
|
+
def initialize(prediction, input_file: nil, page_id: nil)
|
34
|
+
super('receipt', input_file: input_file)
|
35
|
+
@orientation = Orientation.new(prediction['orientation'], page_id) if page_id
|
36
|
+
@locale = Locale.new(prediction['locale'])
|
37
|
+
@total_incl = Amount.new(prediction['total_incl'], page_id)
|
38
|
+
@date = DateField.new(prediction['date'], page_id)
|
39
|
+
@category = Field.new(prediction['category'], page_id)
|
40
|
+
@supplier = Field.new(prediction['supplier'], page_id)
|
41
|
+
@time = Field.new(prediction['time'], page_id)
|
42
|
+
@taxes = []
|
43
|
+
prediction['taxes'].each do |item|
|
44
|
+
@taxes.push(TaxField.new(item, page_id))
|
45
|
+
end
|
46
|
+
|
47
|
+
@total_tax = Amount.new(
|
48
|
+
{ value: nil, confidence: 0.0 }, page_id
|
49
|
+
)
|
50
|
+
@total_excl = Amount.new(
|
51
|
+
{ value: nil, confidence: 0.0 }, page_id
|
52
|
+
)
|
53
|
+
reconstruct(page_id)
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_s
|
57
|
+
taxes = @taxes.join("\n ")
|
58
|
+
out_str = String.new
|
59
|
+
out_str << '-----Receipt data-----'
|
60
|
+
out_str << "\nFilename: #{@filename}".rstrip
|
61
|
+
out_str << "\nTotal amount including taxes: #{@total_incl}".rstrip
|
62
|
+
out_str << "\nTotal amount excluding taxes: #{@total_excl}".rstrip
|
63
|
+
out_str << "\nDate: #{@date}".rstrip
|
64
|
+
out_str << "\nCategory: #{@category}".rstrip
|
65
|
+
out_str << "\nTime: #{@time}".rstrip
|
66
|
+
out_str << "\nMerchant name: #{@supplier}".rstrip
|
67
|
+
out_str << "\nTaxes: #{taxes}".rstrip
|
68
|
+
out_str << "\nTotal taxes: #{@total_tax}".rstrip
|
69
|
+
out_str << "\nLocale: #{@locale}".rstrip
|
70
|
+
out_str << "\n----------------------"
|
71
|
+
out_str
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def reconstruct(page_id)
|
77
|
+
construct_total_tax_from_taxes(page_id)
|
78
|
+
construct_total_excl(page_id)
|
79
|
+
end
|
80
|
+
|
81
|
+
def construct_total_tax_from_taxes(page_id)
|
82
|
+
return if @taxes.empty? || !@total_tax.value.nil?
|
83
|
+
|
84
|
+
total_tax = {
|
85
|
+
'value' => @taxes.map do |tax|
|
86
|
+
if tax.value.nil?
|
87
|
+
0
|
88
|
+
else
|
89
|
+
tax.value
|
90
|
+
end
|
91
|
+
end.sum,
|
92
|
+
'confidence' => Field.array_confidence(@taxes),
|
93
|
+
}
|
94
|
+
return unless total_tax['value'].positive?
|
95
|
+
|
96
|
+
@total_tax = Amount.new(total_tax, page_id, reconstructed: true)
|
97
|
+
end
|
98
|
+
|
99
|
+
def construct_total_excl(page_id)
|
100
|
+
return if @taxes.empty? || @total_incl.value.nil?
|
101
|
+
|
102
|
+
total_excl = {
|
103
|
+
'value' => @total_incl.value - Field.array_sum(@taxes),
|
104
|
+
'confidence' => Field.array_confidence(@taxes) * @total_incl.confidence,
|
105
|
+
}
|
106
|
+
@total_excl = Amount.new(total_excl, page_id, reconstructed: true)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require_relative 'version'
|
5
|
+
|
6
|
+
module Mindee
|
7
|
+
MINDEE_API_URL = 'https://api.mindee.net/v1'
|
8
|
+
USER_AGENT = "mindee-api-ruby@v#{Mindee::VERSION} ruby-v#{RUBY_VERSION} #{Mindee::PLATFORM}"
|
9
|
+
|
10
|
+
INVOICE_VERSION = '3'
|
11
|
+
INVOICE_URL_NAME = 'invoices'
|
12
|
+
|
13
|
+
RECEIPT_VERSION = '3'
|
14
|
+
RECEIPT_URL_NAME = 'expense_receipts'
|
15
|
+
|
16
|
+
PASSPORT_VERSION = '1'
|
17
|
+
PASSPORT_URL_NAME = 'passport'
|
18
|
+
|
19
|
+
# Generic API endpoint for a product.
|
20
|
+
class Endpoint
|
21
|
+
attr_reader :api_key
|
22
|
+
|
23
|
+
def initialize(owner, url_name, version, key_name: nil, api_key: nil)
|
24
|
+
@owner = owner
|
25
|
+
@url_name = url_name
|
26
|
+
@version = version
|
27
|
+
@key_name = key_name || url_name
|
28
|
+
@api_key = api_key || set_api_key_from_env
|
29
|
+
@url_root = "#{MINDEE_API_URL}/products/#{@owner}/#{@url_name}/v#{@version}"
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param input_doc [Mindee::InputDocument]
|
33
|
+
# @param include_words [Boolean]
|
34
|
+
# @param close_file [Boolean]
|
35
|
+
# @return [Net::HTTPResponse]
|
36
|
+
def predict_request(input_doc, include_words: false, close_file: true)
|
37
|
+
uri = URI("#{@url_root}/predict")
|
38
|
+
headers = {
|
39
|
+
'Authorization' => "Token #{@api_key}",
|
40
|
+
'User-Agent' => USER_AGENT,
|
41
|
+
}
|
42
|
+
req = Net::HTTP::Post.new(uri, headers)
|
43
|
+
|
44
|
+
params = {
|
45
|
+
'document' => input_doc.read_document(close: close_file),
|
46
|
+
}
|
47
|
+
params.push ['include_mvision', 'true'] if include_words
|
48
|
+
|
49
|
+
req.set_form(params, 'multipart/form-data')
|
50
|
+
|
51
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
52
|
+
http.request(req)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def envvar_key_name
|
57
|
+
key_name = to_envar(@key_name)
|
58
|
+
key_name = "#{to_envar(@owner)}_#{key_name}" if @owner != 'mindee'
|
59
|
+
"MINDEE_#{key_name}_API_KEY"
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Create a standard way to get/set environment variable names.
|
65
|
+
def to_envar(name)
|
66
|
+
name.sub('-', '_').upcase
|
67
|
+
end
|
68
|
+
|
69
|
+
# Set the endpoint's API key from an environment variable, if present.
|
70
|
+
# We look first for the specific key, if not set, we'll use the generic key
|
71
|
+
def set_api_key_from_env
|
72
|
+
env_key = ENV.fetch(envvar_key_name, nil)
|
73
|
+
env_key = ENV.fetch('MINDEE_API_KEY', nil) if env_key.nil?
|
74
|
+
@api_key = env_key if env_key
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Invoice API endpoint
|
79
|
+
class InvoiceEndpoint < Endpoint
|
80
|
+
def initialize(api_key)
|
81
|
+
super('mindee', INVOICE_URL_NAME, INVOICE_VERSION, key_name: 'invoice', api_key: api_key)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Receipt API endpoint
|
86
|
+
class ReceiptEndpoint < Endpoint
|
87
|
+
def initialize(api_key)
|
88
|
+
super('mindee', RECEIPT_URL_NAME, RECEIPT_VERSION, key_name: 'receipt', api_key: api_key)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Passport API endpoint
|
93
|
+
class PassportEndpoint < Endpoint
|
94
|
+
def initialize(api_key)
|
95
|
+
super('mindee', PASSPORT_URL_NAME, PASSPORT_VERSION, api_key: api_key)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Custom (constructed) API endpoint
|
100
|
+
class CustomEndpoint < Endpoint
|
101
|
+
def initialize(document_type, account_name, version, api_key)
|
102
|
+
super(account_name, document_type, version, api_key: api_key)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Mindee
|
6
|
+
# Represents tax information.
|
7
|
+
class Amount < Field
|
8
|
+
# Amount value as 3 decimal float
|
9
|
+
# @return [Float, nil]
|
10
|
+
attr_reader :value
|
11
|
+
|
12
|
+
def initialize(prediction, page_id, reconstructed: false)
|
13
|
+
super
|
14
|
+
@value = @value.round(3) unless @value.nil?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../geometry'
|
4
|
+
|
5
|
+
module Mindee
|
6
|
+
# Base field object.
|
7
|
+
class Field
|
8
|
+
# @return [String, Float, Integer, Boolean]
|
9
|
+
attr_reader :value
|
10
|
+
# @return [Array<Array<Float>>]
|
11
|
+
attr_reader :bbox
|
12
|
+
# @return [Array<Array<Float>>]
|
13
|
+
attr_reader :polygon
|
14
|
+
# @return [Integer, nil]
|
15
|
+
attr_reader :page_id
|
16
|
+
# true if the field was reconstructed or computed using other fields.
|
17
|
+
# @return [Boolean]
|
18
|
+
attr_reader :reconstructed
|
19
|
+
# The confidence score, value will be between 0.0 and 1.0
|
20
|
+
# @return [Float]
|
21
|
+
attr_accessor :confidence
|
22
|
+
|
23
|
+
# @param prediction [Hash]
|
24
|
+
# @param page_id [Integer, nil]
|
25
|
+
# @param reconstructed [Boolean]
|
26
|
+
def initialize(prediction, page_id, reconstructed: false)
|
27
|
+
@value = prediction['value']
|
28
|
+
@confidence = prediction['confidence']
|
29
|
+
@polygon = prediction['polygon']
|
30
|
+
@bbox = Geometry.get_bbox_as_polygon(@polygon) unless @polygon.nil? || @polygon.empty?
|
31
|
+
@page_id = page_id || prediction['page_id']
|
32
|
+
@reconstructed = reconstructed
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
@value ? @value.to_s : ''
|
37
|
+
end
|
38
|
+
|
39
|
+
# Multiply all the Mindee::Field confidences in the array.
|
40
|
+
def self.array_confidence(field_array)
|
41
|
+
product = 1
|
42
|
+
field_array.each do |field|
|
43
|
+
return 0.0 if field.confidence.nil?
|
44
|
+
|
45
|
+
product *= field.confidence
|
46
|
+
end
|
47
|
+
product.to_f
|
48
|
+
end
|
49
|
+
|
50
|
+
# Add all the Mindee::Field values in the array.
|
51
|
+
def self.array_sum(field_array)
|
52
|
+
arr_sum = 0
|
53
|
+
field_array.each do |field|
|
54
|
+
return 0.0 if field.value.nil?
|
55
|
+
|
56
|
+
arr_sum += field.value
|
57
|
+
end
|
58
|
+
arr_sum.to_f
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mindee
|
4
|
+
# Company registration number or code, and its type.
|
5
|
+
class CompanyRegistration < Field
|
6
|
+
# @return [String]
|
7
|
+
attr_reader :type
|
8
|
+
|
9
|
+
# @param prediction [Hash]
|
10
|
+
# @param page_id [Integer, nil]
|
11
|
+
# @param reconstructed [Boolean]
|
12
|
+
def initialize(prediction, page_id, reconstructed: false)
|
13
|
+
super
|
14
|
+
@type = prediction['type']
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
require_relative 'base'
|
6
|
+
|
7
|
+
module Mindee
|
8
|
+
# Represents a date.
|
9
|
+
class DateField < Field
|
10
|
+
# The date as a standard Ruby `Date` object.
|
11
|
+
# @return [Date, nil]
|
12
|
+
attr_reader :date_object
|
13
|
+
# The ISO 8601 representation of the date, regardless of the `raw` contents.
|
14
|
+
# @return [String, nil]
|
15
|
+
attr_reader :value
|
16
|
+
# The textual representation of the date as found on the document.
|
17
|
+
# @return [String, nil]
|
18
|
+
attr_reader :raw
|
19
|
+
|
20
|
+
# @param prediction [Hash]
|
21
|
+
# @param page_id [Integer, nil]
|
22
|
+
def initialize(prediction, page_id)
|
23
|
+
super
|
24
|
+
return unless @value
|
25
|
+
|
26
|
+
@date_object = Date.parse(@value)
|
27
|
+
@raw = prediction['raw']
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|