mindee 1.2.0 → 2.1.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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.rubocop.yml +2 -2
  4. data/.yardopts +4 -0
  5. data/CHANGELOG.md +26 -0
  6. data/README.md +46 -23
  7. data/Rakefile +6 -1
  8. data/bin/mindee.rb +78 -61
  9. data/docs/ruby-api-builder.md +124 -0
  10. data/docs/ruby-getting-started.md +265 -0
  11. data/docs/ruby-invoice-ocr.md +260 -0
  12. data/docs/ruby-passport-ocr.md +156 -0
  13. data/docs/ruby-receipt-ocr.md +170 -0
  14. data/lib/mindee/client.rb +132 -93
  15. data/lib/mindee/document_config.rb +29 -169
  16. data/lib/mindee/geometry.rb +105 -8
  17. data/lib/mindee/http/endpoint.rb +80 -0
  18. data/lib/mindee/input/pdf_processing.rb +106 -0
  19. data/lib/mindee/input/sources.rb +97 -0
  20. data/lib/mindee/input.rb +3 -0
  21. data/lib/mindee/parsing/document.rb +31 -0
  22. data/lib/mindee/parsing/error.rb +22 -0
  23. data/lib/mindee/parsing/inference.rb +53 -0
  24. data/lib/mindee/parsing/page.rb +46 -0
  25. data/lib/mindee/parsing/prediction/base.rb +30 -0
  26. data/lib/mindee/{fields → parsing/prediction/common_fields}/amount.rb +5 -1
  27. data/lib/mindee/{fields → parsing/prediction/common_fields}/base.rb +16 -5
  28. data/lib/mindee/{fields → parsing/prediction/common_fields}/company_registration.rb +0 -0
  29. data/lib/mindee/{fields/datefield.rb → parsing/prediction/common_fields/date.rb} +0 -0
  30. data/lib/mindee/{fields → parsing/prediction/common_fields}/locale.rb +0 -0
  31. data/lib/mindee/{fields → parsing/prediction/common_fields}/payment_details.rb +0 -0
  32. data/lib/mindee/parsing/prediction/common_fields/position.rb +39 -0
  33. data/lib/mindee/{fields → parsing/prediction/common_fields}/tax.rb +7 -2
  34. data/lib/mindee/parsing/prediction/common_fields/text.rb +12 -0
  35. data/lib/mindee/parsing/prediction/common_fields.rb +11 -0
  36. data/lib/mindee/parsing/prediction/custom/custom_v1.rb +58 -0
  37. data/lib/mindee/{fields/custom_docs.rb → parsing/prediction/custom/fields.rb} +5 -5
  38. data/lib/mindee/parsing/prediction/eu/license_plate/license_plate_v1.rb +34 -0
  39. data/lib/mindee/parsing/prediction/financial_document/financial_document_v1.rb +245 -0
  40. data/lib/mindee/parsing/prediction/financial_document/invoice_line_item.rb +58 -0
  41. data/lib/mindee/parsing/prediction/fr/bank_account_details/bank_account_details_v1.rb +40 -0
  42. data/lib/mindee/parsing/prediction/fr/carte_vitale/carte_vitale_v1.rb +49 -0
  43. data/lib/mindee/parsing/prediction/fr/id_card/id_card_v1.rb +84 -0
  44. data/lib/mindee/parsing/prediction/invoice/invoice_line_item.rb +58 -0
  45. data/lib/mindee/parsing/prediction/invoice/invoice_v4.rb +216 -0
  46. data/lib/mindee/parsing/prediction/passport/passport_v1.rb +184 -0
  47. data/lib/mindee/parsing/prediction/proof_of_address/proof_of_address_v1.rb +82 -0
  48. data/lib/mindee/parsing/prediction/receipt/receipt_v4.rb +87 -0
  49. data/lib/mindee/parsing/prediction/shipping_container/shipping_container_v1.rb +38 -0
  50. data/lib/mindee/parsing/prediction/us/bank_check/bank_check_v1.rb +70 -0
  51. data/lib/mindee/parsing/prediction.rb +14 -0
  52. data/lib/mindee/parsing.rb +4 -0
  53. data/lib/mindee/version.rb +1 -1
  54. data/mindee.gemspec +2 -1
  55. metadata +60 -24
  56. data/lib/mindee/documents/base.rb +0 -35
  57. data/lib/mindee/documents/custom.rb +0 -65
  58. data/lib/mindee/documents/financial_doc.rb +0 -135
  59. data/lib/mindee/documents/invoice.rb +0 -162
  60. data/lib/mindee/documents/passport.rb +0 -163
  61. data/lib/mindee/documents/receipt.rb +0 -109
  62. data/lib/mindee/documents.rb +0 -7
  63. data/lib/mindee/endpoint.rb +0 -105
  64. data/lib/mindee/fields/orientation.rb +0 -26
  65. data/lib/mindee/fields.rb +0 -11
  66. data/lib/mindee/inputs.rb +0 -153
  67. data/lib/mindee/response.rb +0 -27
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../common_fields/base'
4
+
5
+ module Mindee
6
+ # Line items for invoices
7
+ class InvoiceLineItem
8
+ # @return [String] The product code referring to the item.
9
+ attr_reader :product_code
10
+ # @return [String]
11
+ attr_reader :description
12
+ # @return [Float]
13
+ attr_reader :quantity
14
+ # @return [Float]
15
+ attr_reader :unit_price
16
+ # @return [Float]
17
+ attr_reader :total_amount
18
+ # @return [Float] The item tax rate percentage.
19
+ attr_reader :tax_rate
20
+ # @return [Float]
21
+ attr_reader :tax_amount
22
+ # @return [Float]
23
+ attr_reader :confidence
24
+ # @return [Integer]
25
+ attr_reader :page_id
26
+ # @return [Mindee::Geometry::Quadrilateral]
27
+ attr_reader :bounding_box
28
+ # @return [Array<Mindee::Geometry::Polygon>]
29
+ attr_reader :polygon
30
+
31
+ def initialize(prediction, page_id)
32
+ @product_code = prediction['product_code']
33
+ @quantity = prediction['quantity']
34
+ @unit_price = prediction['unit_price']
35
+ @total_amount = prediction['total_amount']
36
+ @tax_amount = prediction['tax_amount']
37
+ @tax_rate = prediction['tax_rate']
38
+ @description = prediction['description']
39
+ @page_id = page_id
40
+ end
41
+
42
+ def to_s
43
+ tax = Field.float_to_string(@tax_amount)
44
+ tax << " (#{Field.float_to_string(@tax_rate)}%)" unless @tax_rate.nil?
45
+
46
+ description = @description.nil? ? '' : @description
47
+ description = "#{description[0..32]}..." if description.size > 35
48
+
49
+ out_str = String.new
50
+ out_str << format('%- 22s', @product_code)
51
+ out_str << " #{format('%- 8s', Field.float_to_string(@quantity))}"
52
+ out_str << " #{format('%- 9s', Field.float_to_string(@unit_price))}"
53
+ out_str << " #{format('%- 10s', Field.float_to_string(@total_amount))}"
54
+ out_str << " #{format('%- 18s', tax)}"
55
+ out_str << " #{description}"
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../common_fields'
4
+ require_relative '../../base'
5
+
6
+ module Mindee
7
+ module Prediction
8
+ module FR
9
+ # French bank account information (RIB)
10
+ class BankAccountDetailsV1 < Prediction
11
+ # The account's IBAN.
12
+ # @return [Mindee::TextField]
13
+ attr_reader :iban
14
+ # The account holder's name.
15
+ # @return [Mindee::TextField]
16
+ attr_reader :account_holder_name
17
+ # The bank's SWIFT code.
18
+ # @return [Mindee::TextField]
19
+ attr_reader :swift
20
+
21
+ # @param prediction [Hash]
22
+ # @param page_id [Integer, nil]
23
+ def initialize(prediction, page_id)
24
+ super
25
+ @iban = TextField.new(prediction['iban'], page_id)
26
+ @account_holder_name = TextField.new(prediction['account_holder_name'], page_id)
27
+ @swift = TextField.new(prediction['swift'], page_id)
28
+ end
29
+
30
+ def to_s
31
+ out_str = String.new
32
+ out_str << "\n:IBAN: #{@iban}".rstrip
33
+ out_str << "\n:Account holder name: #{@account_holder_name}".rstrip
34
+ out_str << "\n:SWIFT: #{@swift}".rstrip
35
+ out_str[1..].to_s
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../common_fields'
4
+ require_relative '../../base'
5
+
6
+ module Mindee
7
+ module Prediction
8
+ module FR
9
+ # French Carte Vitale
10
+ class CarteVitaleV1 < Prediction
11
+ # List of given (first) names of the cardholder.
12
+ # @return [Array<Mindee::TextField>]
13
+ attr_reader :given_names
14
+ # The surname (last name) of the cardholder.
15
+ # @return [Mindee::TextField]
16
+ attr_reader :surname
17
+ # The social security number of the cardholder.
18
+ # @return [Mindee::TextField]
19
+ attr_reader :social_security
20
+ # The issuance date of the card.
21
+ # @return [Mindee::DateField]
22
+ attr_reader :issuance_date
23
+
24
+ # @param prediction [Hash]
25
+ # @param page_id [Integer, nil]
26
+ def initialize(prediction, page_id)
27
+ super
28
+ @given_names = []
29
+ prediction['given_names'].each do |item|
30
+ @given_names.push(TextField.new(item, page_id))
31
+ end
32
+ @surname = TextField.new(prediction['surname'], page_id)
33
+ @social_security = TextField.new(prediction['social_security'], page_id)
34
+ @issuance_date = DateField.new(prediction['issuance_date'], page_id)
35
+ end
36
+
37
+ def to_s
38
+ given_names = @given_names.map(&:value).join(', ')
39
+ out_str = String.new
40
+ out_str << "\n:Given names: #{given_names}".rstrip
41
+ out_str << "\n:Surname: #{@surname}".rstrip
42
+ out_str << "\n:Social Security Number: #{@social_security}".rstrip
43
+ out_str << "\n:Issuance date: #{@issuance_date}".rstrip
44
+ out_str[1..].to_s
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../common_fields'
4
+ require_relative '../../base'
5
+
6
+ module Mindee
7
+ module Prediction
8
+ module FR
9
+ # French national ID card
10
+ class IdCardV1 < Prediction
11
+ # The authority which has issued the card.
12
+ # @return [Array<Mindee::TextField>]
13
+ attr_reader :authority
14
+ # Indicates if it is the recto side, the verso side or both.
15
+ # @return [Mindee::TextField]
16
+ attr_reader :document_side
17
+ # The card number.
18
+ # @return [Mindee::TextField]
19
+ attr_reader :id_number
20
+ # The expiration date of the card.
21
+ # @return [Mindee::DateField]
22
+ attr_reader :expiry_date
23
+ # The surname (last name) of the cardholder.
24
+ # @return [Mindee::TextField]
25
+ attr_reader :surname
26
+ # List of first (given) names of the cardholder.
27
+ # @return [Mindee::TextField]
28
+ attr_reader :given_names
29
+ # The date of birth of the cardholder.
30
+ # @return [Mindee::DateField]
31
+ attr_reader :birth_date
32
+ # The place of birth of the cardholder.
33
+ # @return [Mindee::TextField]
34
+ attr_reader :birth_place
35
+ # The sex or gender of the cardholder.
36
+ # @return [Mindee::TextField]
37
+ attr_reader :gender
38
+ # The value of the first MRZ line.
39
+ # @return [Mindee::TextField]
40
+ attr_reader :mrz1
41
+ # The value of the second MRZ line.
42
+ # @return [Mindee::TextField]
43
+ attr_reader :mrz2
44
+
45
+ # @param prediction [Hash]
46
+ # @param page_id [Integer, nil]
47
+ def initialize(prediction, page_id)
48
+ super
49
+ @document_side = TextField.new(prediction['document_side'], page_id) if page_id
50
+ @authority = TextField.new(prediction['authority'], page_id)
51
+ @id_number = TextField.new(prediction['id_number'], page_id)
52
+ @birth_date = DateField.new(prediction['birth_date'], page_id)
53
+ @expiry_date = DateField.new(prediction['expiry_date'], page_id)
54
+ @birth_place = TextField.new(prediction['birth_place'], page_id)
55
+ @gender = TextField.new(prediction['gender'], page_id)
56
+ @surname = TextField.new(prediction['surname'], page_id)
57
+ @mrz1 = TextField.new(prediction['mrz1'], page_id)
58
+ @mrz2 = TextField.new(prediction['mrz2'], page_id)
59
+ @given_names = []
60
+ prediction['given_names'].each do |item|
61
+ @given_names.push(TextField.new(item, page_id))
62
+ end
63
+ end
64
+
65
+ def to_s
66
+ given_names = @given_names.map(&:value).join(', ')
67
+ out_str = String.new
68
+ out_str << "\n:Document side: #{@document_side}".rstrip if @document_side
69
+ out_str << "\n:Authority: #{@authority}".rstrip
70
+ out_str << "\n:Given names: #{given_names}".rstrip
71
+ out_str << "\n:Surname: #{@surname}".rstrip
72
+ out_str << "\n:Gender: #{@gender}".rstrip
73
+ out_str << "\n:ID Number: #{@id_number}".rstrip
74
+ out_str << "\n:Birth date: #{@birth_date}".rstrip
75
+ out_str << "\n:Birth place: #{@birth_place}".rstrip
76
+ out_str << "\n:Expiry date: #{@expiry_date}".rstrip
77
+ out_str << "\n:MRZ 1: #{@mrz1}".rstrip
78
+ out_str << "\n:MRZ 2: #{@mrz2}".rstrip
79
+ out_str[1..].to_s
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../common_fields/base'
4
+
5
+ module Mindee
6
+ # Line items for invoices
7
+ class InvoiceLineItem
8
+ # @return [String] The product code referring to the item.
9
+ attr_reader :product_code
10
+ # @return [String]
11
+ attr_reader :description
12
+ # @return [Float]
13
+ attr_reader :quantity
14
+ # @return [Float]
15
+ attr_reader :unit_price
16
+ # @return [Float]
17
+ attr_reader :total_amount
18
+ # @return [Float] The item tax rate percentage.
19
+ attr_reader :tax_rate
20
+ # @return [Float]
21
+ attr_reader :tax_amount
22
+ # @return [Float]
23
+ attr_reader :confidence
24
+ # @return [Integer]
25
+ attr_reader :page_id
26
+ # @return [Mindee::Geometry::Quadrilateral]
27
+ attr_reader :bounding_box
28
+ # @return [Array<Mindee::Geometry::Polygon>]
29
+ attr_reader :polygon
30
+
31
+ def initialize(prediction, page_id)
32
+ @product_code = prediction['product_code']
33
+ @quantity = prediction['quantity']
34
+ @unit_price = prediction['unit_price']
35
+ @total_amount = prediction['total_amount']
36
+ @tax_amount = prediction['tax_amount']
37
+ @tax_rate = prediction['tax_rate']
38
+ @description = prediction['description']
39
+ @page_id = page_id
40
+ end
41
+
42
+ def to_s
43
+ tax = Field.float_to_string(@tax_amount)
44
+ tax << " (#{Field.float_to_string(@tax_rate)}%)" unless @tax_rate.nil?
45
+
46
+ description = @description.nil? ? '' : @description
47
+ description = "#{description[0..32]}..." if description.size > 35
48
+
49
+ out_str = String.new
50
+ out_str << format('%- 22s', @product_code)
51
+ out_str << " #{format('%- 8s', Field.float_to_string(@quantity))}"
52
+ out_str << " #{format('%- 9s', Field.float_to_string(@unit_price))}"
53
+ out_str << " #{format('%- 10s', Field.float_to_string(@total_amount))}"
54
+ out_str << " #{format('%- 18s', tax)}"
55
+ out_str << " #{description}"
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../common_fields'
4
+ require_relative 'invoice_line_item'
5
+ require_relative '../base'
6
+
7
+ module Mindee
8
+ module Prediction
9
+ # Invoice document.
10
+ class InvoiceV4 < Prediction
11
+ # Locale information.
12
+ # @return [Mindee::Locale]
13
+ attr_reader :locale
14
+ # The nature of the invoice.
15
+ # @return [Mindee::TextField]
16
+ attr_reader :document_type
17
+ # The total amount with tax included.
18
+ # @return [Mindee::AmountField]
19
+ attr_reader :total_amount
20
+ # The total amount without the tax value.
21
+ # @return [Mindee::AmountField]
22
+ attr_reader :total_net
23
+ # The total tax.
24
+ # @return [Mindee::AmountField]
25
+ attr_reader :total_tax
26
+ # The creation date of the invoice.
27
+ # @return [Mindee::DateField]
28
+ attr_reader :date
29
+ # The invoice number.
30
+ # @return [Mindee::TextField]
31
+ attr_reader :invoice_number
32
+ # List of Reference numbers including PO number.
33
+ # @return [Mindee::TextField]
34
+ attr_reader :reference_numbers
35
+ # The due date of the invoice.
36
+ # @return [Mindee::DateField]
37
+ attr_reader :due_date
38
+ # The list of taxes.
39
+ # @return [Array<Mindee::TaxField>]
40
+ attr_reader :taxes
41
+ # The name of the customer.
42
+ # @return [Mindee::TextField]
43
+ attr_reader :customer_name
44
+ # The address of the customer.
45
+ # @return [Mindee::TextField]
46
+ attr_reader :customer_address
47
+ # The company registration information for the customer.
48
+ # @return [Array<Mindee::CompanyRegistration>]
49
+ attr_reader :customer_company_registrations
50
+ # The supplier's name.
51
+ # @return [Mindee::TextField]
52
+ attr_reader :supplier_name
53
+ # The supplier's address.
54
+ # @return [Mindee::TextField]
55
+ attr_reader :supplier_address
56
+ # The payment information.
57
+ # @return [Array<Mindee::PaymentDetails>]
58
+ attr_reader :supplier_payment_details
59
+ # The supplier's company registration information.
60
+ # @return [Array<Mindee::CompanyRegistration>]
61
+ attr_reader :supplier_company_registrations
62
+ # Line items details.
63
+ # @return [Array<Mindee::InvoiceLineItem>]
64
+ attr_reader :line_items
65
+
66
+ # @param prediction [Hash]
67
+ # @param page_id [Integer, nil]
68
+ def initialize(prediction, page_id)
69
+ super
70
+ @locale = Locale.new(prediction['locale'])
71
+ @document_type = TextField.new(prediction['document_type'], page_id)
72
+ @total_amount = AmountField.new(prediction['total_amount'], page_id)
73
+ @total_net = AmountField.new(prediction['total_net'], page_id)
74
+ @customer_address = TextField.new(prediction['customer_address'], page_id)
75
+ @customer_name = TextField.new(prediction['customer_name'], page_id)
76
+ @date = DateField.new(prediction['date'], page_id)
77
+ @due_date = DateField.new(prediction['due_date'], page_id)
78
+ @invoice_number = TextField.new(prediction['invoice_number'], page_id)
79
+ @supplier_name = TextField.new(prediction['supplier_name'], page_id)
80
+ @supplier_address = TextField.new(prediction['supplier_address'], page_id)
81
+
82
+ @reference_numbers = []
83
+ prediction['reference_numbers'].each do |item|
84
+ @reference_numbers.push(TextField.new(item, page_id))
85
+ end
86
+ @customer_company_registrations = []
87
+ prediction['customer_company_registrations'].each do |item|
88
+ @customer_company_registrations.push(CompanyRegistration.new(item, page_id))
89
+ end
90
+ @taxes = []
91
+ prediction['taxes'].each do |item|
92
+ @taxes.push(TaxField.new(item, page_id))
93
+ end
94
+ @supplier_payment_details = []
95
+ prediction['supplier_payment_details'].each do |item|
96
+ @supplier_payment_details.push(PaymentDetails.new(item, page_id))
97
+ end
98
+ @supplier_company_registrations = []
99
+ prediction['supplier_company_registrations'].each do |item|
100
+ @supplier_company_registrations.push(CompanyRegistration.new(item, page_id))
101
+ end
102
+
103
+ @total_tax = AmountField.new(
104
+ { value: nil, confidence: 0.0 }, page_id
105
+ )
106
+
107
+ @line_items = []
108
+ prediction['line_items'].each do |item|
109
+ @line_items.push(InvoiceLineItem.new(item, page_id))
110
+ end
111
+ reconstruct(page_id)
112
+ end
113
+
114
+ def to_s
115
+ customer_company_registrations = @customer_company_registrations.map(&:value).join('; ')
116
+ supplier_payment_details = @supplier_payment_details.map(&:to_s).join("\n ")
117
+ supplier_company_registrations = @supplier_company_registrations.map(&:to_s).join('; ')
118
+ reference_numbers = @reference_numbers.map(&:to_s).join(', ')
119
+ taxes = @taxes.join("\n ")
120
+ out_str = String.new
121
+ out_str << "\n:Locale: #{@locale}".rstrip
122
+ out_str << "\n:Document type: #{@document_type}".rstrip
123
+ out_str << "\n:Invoice number: #{@invoice_number}".rstrip
124
+ out_str << "\n:Reference numbers: #{reference_numbers}".rstrip
125
+ out_str << "\n:Invoice date: #{@date}".rstrip
126
+ out_str << "\n:Invoice due date: #{@due_date}".rstrip
127
+
128
+ out_str << "\n:Supplier name: #{@supplier_name}".rstrip
129
+ out_str << "\n:Supplier address: #{@supplier_address}".rstrip
130
+ out_str << "\n:Supplier company registrations: #{supplier_company_registrations}".rstrip
131
+ out_str << "\n:Supplier payment details: #{supplier_payment_details}".rstrip
132
+
133
+ out_str << "\n:Customer name: #{@customer_name}".rstrip
134
+ out_str << "\n:Customer address: #{@customer_address}".rstrip
135
+ out_str << "\n:Customer company registrations: #{customer_company_registrations}".rstrip
136
+
137
+ out_str << "\n:Taxes: #{taxes}".rstrip
138
+ out_str << "\n:Total net: #{@total_net}".rstrip
139
+ out_str << "\n:Total taxes: #{@total_tax}".rstrip
140
+ out_str << "\n:Total amount: #{@total_amount}".rstrip
141
+ out_str << line_items_to_s
142
+
143
+ out_str[1..].to_s
144
+ end
145
+
146
+ private
147
+
148
+ def line_items_to_s
149
+ return '' if @line_items.empty?
150
+
151
+ line_item_separator = "#{'=' * 22} #{'=' * 8} #{'=' * 9} #{'=' * 10} #{'=' * 18} #{'=' * 36}"
152
+ line_items = @line_items.map(&:to_s).join("\n")
153
+ out_str = String.new
154
+ out_str << "\n\n:Line Items:"
155
+ out_str << "\n#{line_item_separator}"
156
+ out_str << "\nCode QTY Price Amount Tax (Rate) Description"
157
+ out_str << "\n#{line_item_separator}"
158
+ out_str << "\n#{line_items}"
159
+ out_str << "\n#{line_item_separator}" unless line_items.empty?
160
+ end
161
+
162
+ def reconstruct(page_id)
163
+ construct_total_tax_from_taxes(page_id)
164
+ return unless page_id.nil?
165
+
166
+ construct_total_excl_from_tcc_and_taxes(page_id)
167
+ construct_total_incl_from_taxes_plus_excl(page_id)
168
+ construct_total_tax_from_totals(page_id)
169
+ end
170
+
171
+ def construct_total_excl_from_tcc_and_taxes(page_id)
172
+ return if @total_amount.value.nil? || taxes.empty? || !@total_net.value.nil?
173
+
174
+ total_excl = {
175
+ 'value' => @total_amount.value - @taxes.map(&:value).sum,
176
+ 'confidence' => TextField.array_confidence(@taxes) * @total_amount.confidence,
177
+ }
178
+ @total_net = AmountField.new(total_excl, page_id, reconstructed: true)
179
+ end
180
+
181
+ def construct_total_incl_from_taxes_plus_excl(page_id)
182
+ return if @total_net.value.nil? || @taxes.empty? || !@total_amount.value.nil?
183
+
184
+ total_incl = {
185
+ 'value' => @taxes.map(&:value).sum + @total_net.value,
186
+ 'confidence' => TextField.array_confidence(@taxes) * @total_net.confidence,
187
+ }
188
+ @total_amount = AmountField.new(total_incl, page_id, reconstructed: true)
189
+ end
190
+
191
+ def construct_total_tax_from_taxes(page_id)
192
+ return if @taxes.empty?
193
+
194
+ total_tax = {
195
+ 'value' => @taxes.map(&:value).sum,
196
+ 'confidence' => TextField.array_confidence(@taxes),
197
+ }
198
+ return unless total_tax['value'].positive?
199
+
200
+ @total_tax = AmountField.new(total_tax, page_id, reconstructed: true)
201
+ end
202
+
203
+ def construct_total_tax_from_totals(page_id)
204
+ return if !@total_tax.value.nil? || @total_amount.value.nil? || @total_net.value.nil?
205
+
206
+ total_tax = {
207
+ 'value' => @total_amount.value - @total_net.value,
208
+ 'confidence' => TextField.array_confidence(@taxes),
209
+ }
210
+ return unless total_tax['value'] >= 0
211
+
212
+ @total_tax = AmountField.new(total_tax, page_id, reconstructed: true)
213
+ end
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mrz'
4
+
5
+ require_relative '../common_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
+ module Prediction
19
+ # Passport document.
20
+ class PassportV1 < Prediction
21
+ # The country of issue.
22
+ # @return [Mindee::TextField]
23
+ attr_reader :country
24
+ # The passport number.
25
+ # @return [Mindee::TextField]
26
+ attr_reader :id_number
27
+ # The expiration date of the passport.
28
+ # @return [Mindee::DateField]
29
+ attr_reader :expiry_date
30
+ # The issuance date of the passport.
31
+ # @return [Mindee::DateField]
32
+ attr_reader :issuance_date
33
+ # The surname (last name) of the passport holder.
34
+ # @return [Mindee::TextField]
35
+ attr_reader :surname
36
+ # List of first (given) names of the passport holder.
37
+ # @return [Mindee::TextField]
38
+ attr_reader :given_names
39
+ # The full name of the passport holder.
40
+ # @return [Array<Mindee::TextField>]
41
+ attr_reader :full_name
42
+ # The date of birth of the passport holder.
43
+ # @return [Mindee::DateField]
44
+ attr_reader :birth_date
45
+ # The place of birth of the passport holder.
46
+ # @return [Mindee::TextField]
47
+ attr_reader :birth_place
48
+ # The sex or gender of the passport holder.
49
+ # @return [Mindee::TextField]
50
+ attr_reader :gender
51
+ # The value of the first MRZ line.
52
+ # @return [Mindee::TextField]
53
+ attr_reader :mrz1
54
+ # The value of the second MRZ line.
55
+ # @return [Mindee::TextField]
56
+ attr_reader :mrz2
57
+ # All the MRZ values combined.
58
+ # @return [Mindee::TextField]
59
+ attr_reader :mrz
60
+
61
+ # @param prediction [Hash]
62
+ # @param page_id [Integer, nil]
63
+ def initialize(prediction, page_id)
64
+ super
65
+ @country = TextField.new(prediction['country'], page_id)
66
+ @id_number = TextField.new(prediction['id_number'], page_id)
67
+ @birth_date = DateField.new(prediction['birth_date'], page_id)
68
+ @expiry_date = DateField.new(prediction['expiry_date'], page_id)
69
+ @issuance_date = DateField.new(prediction['issuance_date'], page_id)
70
+ @birth_place = TextField.new(prediction['birth_place'], page_id)
71
+ @gender = TextField.new(prediction['gender'], page_id)
72
+ @surname = TextField.new(prediction['surname'], page_id)
73
+ @mrz1 = TextField.new(prediction['mrz1'], page_id)
74
+ @mrz2 = TextField.new(prediction['mrz2'], page_id)
75
+ @given_names = []
76
+ prediction['given_names'].each do |item|
77
+ @given_names.push(TextField.new(item, page_id))
78
+ end
79
+ @full_name = construct_full_name(page_id)
80
+ @mrz = construct_mrz(page_id)
81
+ check_mrz
82
+ end
83
+
84
+ def to_s
85
+ given_names = @given_names.join(' ')
86
+ out_str = String.new
87
+ out_str << "\n:Full name: #{@full_name}".rstrip
88
+ out_str << "\n:Given names: #{given_names}".rstrip
89
+ out_str << "\n:Surname: #{@surname}".rstrip
90
+ out_str << "\n:Country: #{@country}".rstrip
91
+ out_str << "\n:ID Number: #{@id_number}".rstrip
92
+ out_str << "\n:Issuance date: #{@issuance_date}".rstrip
93
+ out_str << "\n:Birth date: #{@birth_date}".rstrip
94
+ out_str << "\n:Expiry date: #{@expiry_date}".rstrip
95
+ out_str << "\n:MRZ 1: #{@mrz1}".rstrip
96
+ out_str << "\n:MRZ 2: #{@mrz2}".rstrip
97
+ out_str << "\n:MRZ: #{@mrz}".rstrip
98
+ out_str[1..].to_s
99
+ end
100
+
101
+ # @return [Boolean]
102
+ def expired?
103
+ return true unless @expiry_date.date_object
104
+
105
+ @expiry_date.date_object < Date.today
106
+ end
107
+
108
+ private
109
+
110
+ def check_mrz
111
+ return if @mrz1.value.nil? || @mrz2.value.nil?
112
+
113
+ mrz = MRZ.parse([@mrz1.value, @mrz2.value])
114
+ checks = {
115
+ mrz_valid: valid_mrz?(mrz),
116
+ mrz_valid_birth_date: valid_birth_date?(mrz),
117
+ mrz_valid_expiry_date: valid_expiry_date?(mrz),
118
+ mrz_valid_id_number: valid_id_number?(mrz),
119
+ mrz_valid_surname: valid_surname?(mrz),
120
+ mrz_valid_country: valid_country?(mrz),
121
+ }
122
+ @checklist.merge!(checks)
123
+ end
124
+
125
+ def valid_mrz?(mrz)
126
+ check = mrz.valid?
127
+ @mrz.confidence = 1.0 if check
128
+ check
129
+ end
130
+
131
+ def valid_birth_date?(mrz)
132
+ check = mrz.valid_birth_date? && mrz.birth_date == @birth_date.date_object
133
+ @birth_date.confidence = 1.0 if check
134
+ check
135
+ end
136
+
137
+ def valid_expiry_date?(mrz)
138
+ check = mrz.valid_expiration_date? && mrz.expiration_date == @expiry_date.date_object
139
+ @expiry_date.confidence = 1.0 if check
140
+ check
141
+ end
142
+
143
+ def valid_id_number?(mrz)
144
+ check = mrz.valid_document_number? && mrz.document_number == @id_number.value
145
+ @id_number.confidence = 1.0 if check
146
+ check
147
+ end
148
+
149
+ def valid_surname?(mrz)
150
+ check = mrz.last_name == @surname.value
151
+ @surname.confidence = 1.0 if check
152
+ check
153
+ end
154
+
155
+ def valid_country?(mrz)
156
+ check = mrz.nationality == @country.value
157
+ @country.confidence = 1.0 if check
158
+ check
159
+ end
160
+
161
+ def construct_full_name(page_id)
162
+ return unless @surname.value &&
163
+ !@given_names.empty? &&
164
+ @given_names[0].value
165
+
166
+ full_name = {
167
+ 'value' => "#{@given_names[0].value} #{@surname.value}",
168
+ 'confidence' => TextField.array_confidence([@surname, @given_names[0]]),
169
+ }
170
+ TextField.new(full_name, page_id, reconstructed: true)
171
+ end
172
+
173
+ def construct_mrz(page_id)
174
+ return if @mrz1.value.nil? || @mrz2.value.nil?
175
+
176
+ mrz = {
177
+ 'value' => @mrz1.value + @mrz2.value,
178
+ 'confidence' => Mindee::TextField.array_confidence([@mrz1, @mrz2]),
179
+ }
180
+ TextField.new(mrz, page_id, reconstructed: true)
181
+ end
182
+ end
183
+ end
184
+ end