mindee 1.0.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 +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
|