mindee 1.1.2 → 2.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.
Files changed (65) 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 +25 -0
  6. data/Gemfile +0 -7
  7. data/README.md +52 -21
  8. data/Rakefile +6 -1
  9. data/bin/mindee.rb +70 -61
  10. data/docs/ruby-api-builder.md +131 -0
  11. data/docs/ruby-getting-started.md +265 -0
  12. data/docs/ruby-invoice-ocr.md +261 -0
  13. data/docs/ruby-passport-ocr.md +156 -0
  14. data/docs/ruby-receipt-ocr.md +170 -0
  15. data/lib/mindee/client.rb +128 -93
  16. data/lib/mindee/document_config.rb +22 -154
  17. data/lib/mindee/geometry.rb +105 -8
  18. data/lib/mindee/http/endpoint.rb +80 -0
  19. data/lib/mindee/input/pdf_processing.rb +106 -0
  20. data/lib/mindee/input/sources.rb +97 -0
  21. data/lib/mindee/input.rb +3 -0
  22. data/lib/mindee/parsing/document.rb +31 -0
  23. data/lib/mindee/parsing/error.rb +22 -0
  24. data/lib/mindee/parsing/inference.rb +53 -0
  25. data/lib/mindee/parsing/page.rb +46 -0
  26. data/lib/mindee/parsing/prediction/base.rb +30 -0
  27. data/lib/mindee/{fields → parsing/prediction/common_fields}/amount.rb +5 -1
  28. data/lib/mindee/{fields → parsing/prediction/common_fields}/base.rb +16 -5
  29. data/lib/mindee/{fields → parsing/prediction/common_fields}/company_registration.rb +0 -0
  30. data/lib/mindee/{fields/datefield.rb → parsing/prediction/common_fields/date.rb} +0 -0
  31. data/lib/mindee/{fields → parsing/prediction/common_fields}/locale.rb +0 -0
  32. data/lib/mindee/{fields → parsing/prediction/common_fields}/payment_details.rb +0 -0
  33. data/lib/mindee/parsing/prediction/common_fields/position.rb +39 -0
  34. data/lib/mindee/{fields → parsing/prediction/common_fields}/tax.rb +7 -2
  35. data/lib/mindee/parsing/prediction/common_fields/text.rb +12 -0
  36. data/lib/mindee/parsing/prediction/common_fields.rb +11 -0
  37. data/lib/mindee/parsing/prediction/custom/custom_v1.rb +58 -0
  38. data/lib/mindee/{fields/custom_docs.rb → parsing/prediction/custom/fields.rb} +5 -5
  39. data/lib/mindee/parsing/prediction/eu/license_plate/license_plate_v1.rb +34 -0
  40. data/lib/mindee/parsing/prediction/fr/bank_account_details/bank_account_details_v1.rb +40 -0
  41. data/lib/mindee/parsing/prediction/fr/carte_vitale/carte_vitale_v1.rb +49 -0
  42. data/lib/mindee/parsing/prediction/fr/id_card/id_card_v1.rb +84 -0
  43. data/lib/mindee/parsing/prediction/invoice/invoice_line_item.rb +58 -0
  44. data/lib/mindee/parsing/prediction/invoice/invoice_v4.rb +216 -0
  45. data/lib/mindee/parsing/prediction/passport/passport_v1.rb +184 -0
  46. data/lib/mindee/parsing/prediction/receipt/receipt_v4.rb +84 -0
  47. data/lib/mindee/parsing/prediction/shipping_container/shipping_container_v1.rb +38 -0
  48. data/lib/mindee/parsing/prediction/us/bank_check/bank_check_v1.rb +70 -0
  49. data/lib/mindee/parsing/prediction.rb +12 -0
  50. data/lib/mindee/parsing.rb +4 -0
  51. data/lib/mindee/version.rb +1 -1
  52. data/mindee.gemspec +11 -5
  53. metadata +105 -30
  54. data/lib/mindee/documents/base.rb +0 -35
  55. data/lib/mindee/documents/custom.rb +0 -65
  56. data/lib/mindee/documents/financial_doc.rb +0 -135
  57. data/lib/mindee/documents/invoice.rb +0 -162
  58. data/lib/mindee/documents/passport.rb +0 -163
  59. data/lib/mindee/documents/receipt.rb +0 -109
  60. data/lib/mindee/documents.rb +0 -7
  61. data/lib/mindee/endpoint.rb +0 -105
  62. data/lib/mindee/fields/orientation.rb +0 -26
  63. data/lib/mindee/fields.rb +0 -11
  64. data/lib/mindee/inputs.rb +0 -153
  65. data/lib/mindee/response.rb +0 -27
@@ -3,19 +3,116 @@
3
3
  module Mindee
4
4
  # Various helper functions for geometry.
5
5
  module Geometry
6
+ # A relative set of coordinates (X, Y) on the document.
7
+ class Point
8
+ # @return [Float]
9
+ attr_accessor :x
10
+ # @return [Float]
11
+ attr_accessor :y
12
+
13
+ # @param x [Float]
14
+ # @param y [Float]
15
+ # rubocop:disable Naming/MethodParameterName
16
+ def initialize(x, y)
17
+ @x = x
18
+ @y = y
19
+ end
20
+ # rubocop:enable Naming/MethodParameterName
21
+
22
+ # @return [Float]
23
+ def [](key)
24
+ case key
25
+ when 0
26
+ @x
27
+ when 1
28
+ @y
29
+ else
30
+ throw '0 or 1 only'
31
+ end
32
+ end
33
+ end
34
+
35
+ # Contains exactly 4 relative vertices coordinates (Points).
36
+ class Quadrilateral
37
+ # @return [Mindee::Geometry::Point]
38
+ attr_accessor :top_left
39
+ # @return [Mindee::Geometry::Point]
40
+ attr_accessor :top_right
41
+ # @return [Mindee::Geometry::Point]
42
+ attr_accessor :bottom_right
43
+ # @return [Mindee::Geometry::Point]
44
+ attr_accessor :bottom_left
45
+
46
+ # @param top_left [Mindee::Geometry::Point]
47
+ # @param top_right [Mindee::Geometry::Point]
48
+ # @param bottom_right [Mindee::Geometry::Point]
49
+ # @param bottom_left [Mindee::Geometry::Point]
50
+ def initialize(top_left, top_right, bottom_right, bottom_left)
51
+ @top_left = top_left
52
+ @top_right = top_right
53
+ @bottom_right = bottom_right
54
+ @bottom_left = bottom_left
55
+ end
56
+
57
+ # @return [Mindee::Geometry::Point]
58
+ def [](key)
59
+ case key
60
+ when 0
61
+ @top_left
62
+ when 1
63
+ @top_right
64
+ when 2
65
+ @bottom_right
66
+ when 3
67
+ @bottom_left
68
+ else
69
+ throw '0, 1, 2, 3 only'
70
+ end
71
+ end
72
+ end
73
+
74
+ class Polygon < Array
75
+ end
76
+
77
+ # Transform a prediction into a Quadrilateral.
78
+ def self.quadrilateral_from_prediction(prediction)
79
+ throw "Prediction must have exactly 4 points, found #{prediction.size}" if prediction.size != 4
80
+
81
+ Quadrilateral.new(
82
+ Point.new(prediction[0][0], prediction[0][1]),
83
+ Point.new(prediction[1][0], prediction[1][1]),
84
+ Point.new(prediction[2][0], prediction[2][1]),
85
+ Point.new(prediction[3][0], prediction[3][1])
86
+ )
87
+ end
88
+
89
+ # Transform a prediction into a Polygon.
90
+ def self.polygon_from_prediction(prediction)
91
+ polygon = Polygon.new
92
+ return polygon if prediction.nil?
93
+
94
+ prediction.each do |point|
95
+ polygon << Point.new(point[0], point[1])
96
+ end
97
+ polygon
98
+ end
99
+
6
100
  # @return [Array<Float>]
7
101
  def self.get_bbox(vertices)
8
- x_min = vertices.map { |v| v[0] }.min
9
- x_max = vertices.map { |v| v[0] }.max
10
- y_min = vertices.map { |v| v[1] }.min
11
- y_max = vertices.map { |v| v[1] }.max
12
- [x_min, y_min, x_max, y_max]
102
+ x_coords = vertices.map(&:x)
103
+ y_coords = vertices.map(&:y)
104
+ [x_coords.min, y_coords.min, x_coords.max, y_coords.max]
13
105
  end
14
106
 
15
- # @return [Array<Array<Float>>]
16
- def self.get_bbox_as_polygon(vertices)
107
+ # @return [Mindee::Geometry::Quadrilateral]
108
+ def self.get_bounding_box(vertices)
17
109
  x_min, y_min, x_max, y_max = get_bbox(vertices)
18
- [[x_min, y_min], [x_max, y_min], [x_max, y_max], [x_min, y_max]]
110
+ Quadrilateral.new(
111
+ Point.new(x_min, y_min),
112
+ Point.new(x_max, y_min),
113
+ Point.new(x_max, y_max),
114
+ Point.new(x_min, y_max)
115
+ )
19
116
  end
20
117
  end
21
118
  end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require_relative '../version'
5
+
6
+ module Mindee
7
+ module HTTP
8
+ API_KEY_ENV_NAME = 'MINDEE_API_KEY'
9
+ API_KEY_DEFAULT = nil
10
+
11
+ BASE_URL_ENV_NAME = 'MINDEE_BASE_URL'
12
+ BASE_URL_DEFAULT = 'https://api.mindee.net/v1'
13
+
14
+ REQUEST_TIMEOUT_ENV_NAME = 'MINDEE_REQUEST_TIMEOUT'
15
+ TIMEOUT_DEFAULT = 120
16
+
17
+ USER_AGENT = "mindee-api-ruby@v#{Mindee::VERSION} ruby-v#{RUBY_VERSION} #{Mindee::PLATFORM}"
18
+
19
+ # Generic API endpoint for a product.
20
+ class Endpoint
21
+ # @return [String]
22
+ attr_reader :api_key
23
+ # @return [Integer]
24
+ attr_reader :request_timeout
25
+
26
+ def initialize(owner, url_name, version, api_key: '')
27
+ @owner = owner
28
+ @url_name = url_name
29
+ @version = version
30
+ @request_timeout = ENV.fetch(REQUEST_TIMEOUT_ENV_NAME, TIMEOUT_DEFAULT).to_i
31
+ @api_key = api_key.nil? || api_key.empty? ? ENV.fetch(API_KEY_ENV_NAME, API_KEY_DEFAULT) : api_key
32
+ @url_root = "#{BASE_URL_DEFAULT}/products/#{@owner}/#{@url_name}/v#{@version}"
33
+ end
34
+
35
+ # @param input_doc [Mindee::InputDocument]
36
+ # @param include_words [Boolean]
37
+ # @param close_file [Boolean]
38
+ # @param cropper [Boolean]
39
+ # @return [Net::HTTPResponse]
40
+ def predict_req_post(input_doc, include_words: false, close_file: true, cropper: false)
41
+ uri = URI("#{@url_root}/predict")
42
+
43
+ params = {}
44
+ params[:cropper] = 'true' if cropper
45
+ uri.query = URI.encode_www_form(params)
46
+
47
+ headers = {
48
+ 'Authorization' => "Token #{@api_key}",
49
+ 'User-Agent' => USER_AGENT,
50
+ }
51
+ req = Net::HTTP::Post.new(uri, headers)
52
+
53
+ form_data = {
54
+ 'document' => input_doc.read_document(close: close_file),
55
+ }
56
+ form_data.push ['include_mvision', 'true'] if include_words
57
+
58
+ req.set_form(form_data, 'multipart/form-data')
59
+
60
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, read_timeout: @request_timeout) do |http|
61
+ http.request(req)
62
+ end
63
+ end
64
+ end
65
+
66
+ # Receipt API endpoint
67
+ class StandardEndpoint < Endpoint
68
+ def initialize(endpoint_name, version, api_key)
69
+ super('mindee', endpoint_name, version, api_key: api_key)
70
+ end
71
+ end
72
+
73
+ # Custom (constructed) API endpoint
74
+ class CustomEndpoint < Endpoint
75
+ def initialize(account_name, endpoint_name, version, api_key)
76
+ super(account_name, endpoint_name, version, api_key: api_key)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require 'origami'
5
+
6
+ # Monkey-patching for Origami
7
+ module PDFTools
8
+ def to_io_stream(params = {})
9
+ options = {
10
+ delinearize: true,
11
+ recompile: true,
12
+ decrypt: false,
13
+ }
14
+ options.update(params)
15
+
16
+ if frozen? # incompatible flags with frozen doc (signed)
17
+ options[:recompile] = nil
18
+ options[:rebuild_xrefs] = nil
19
+ options[:noindent] = nil
20
+ options[:obfuscate] = false
21
+ end
22
+ load_all_objects unless @loaded
23
+
24
+ intents_as_pdfa1 if options[:intent] =~ %r{pdf[/-]?A1?/i}
25
+ delinearize! if options[:delinearize] && linearized?
26
+ compile(options) if options[:recompile]
27
+
28
+ io_stream = StringIO.new(output(options))
29
+ io_stream.set_encoding Encoding::BINARY
30
+ io_stream
31
+ end
32
+ end
33
+
34
+ Origami::PDF.class_eval { include PDFTools }
35
+
36
+ module Mindee
37
+ module Input
38
+ # Class for PDF documents
39
+ module PdfProcessor
40
+ DEFAULT_OPTIONS = {
41
+ page_indexes: [0],
42
+ operation: :KEEP_ONLY,
43
+ on_min_pages: 0,
44
+ }.freeze
45
+
46
+ # @param io_stream [StreamIO]
47
+ # @param options [Hash]
48
+ def self.parse(io_stream, options)
49
+ options = DEFAULT_OPTIONS.merge(options)
50
+
51
+ current_pdf = open_pdf(io_stream)
52
+ pages_count = current_pdf.pages.size
53
+ return if options[:on_min_pages] > pages_count
54
+
55
+ all_pages = (0..pages_count - 1).to_a
56
+
57
+ case options[:operation]
58
+ when :KEEP_ONLY
59
+ pages_to_remove = indexes_from_keep(options[:page_indexes], all_pages)
60
+ when :REMOVE
61
+ pages_to_remove = indexes_from_remove(options[:page_indexes], all_pages)
62
+ else
63
+ raise "operation must be one of :KEEP_ONLY or :REMOVE, sent '#{behavior}'"
64
+ end
65
+
66
+ current_pdf.delete_pages_at(pages_to_remove) if pages_to_remove.to_a != all_pages.to_a
67
+ current_pdf.to_io_stream
68
+ end
69
+
70
+ # @param page_indexes [Array]
71
+ # @param all_pages [Array]
72
+ def self.indexes_from_keep(page_indexes, all_pages)
73
+ pages_to_keep = Set.new
74
+ page_indexes.each do |idx|
75
+ idx = (all_pages.length - (idx + 2)) if idx.negative?
76
+ page = all_pages[idx]
77
+ next if page.nil?
78
+
79
+ pages_to_keep << page
80
+ end
81
+ all_pages.to_set - pages_to_keep
82
+ end
83
+
84
+ # @param page_indexes [Array]
85
+ # @param all_pages [Array]
86
+ def self.indexes_from_remove(page_indexes, all_pages)
87
+ pages_to_remove = Set.new
88
+ page_indexes.each do |idx|
89
+ idx = (all_pages.length - (idx + 2)) if idx.negative?
90
+ page = all_pages[idx]
91
+ next if page.nil?
92
+
93
+ pages_to_remove << page
94
+ end
95
+ end
96
+
97
+ # @param io_stream [StringIO]
98
+ # @return [Origami::PDF]
99
+ def self.open_pdf(io_stream)
100
+ pdf_parser = Origami::PDF::LinearParser.new({ verbosity: Origami::Parser::VERBOSE_QUIET })
101
+ io_stream.seek(0)
102
+ pdf_parser.parse(io_stream)
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+ require 'marcel'
5
+
6
+ require_relative 'pdf_processing'
7
+
8
+ module Mindee
9
+ module Input
10
+ ALLOWED_MIME_TYPES = [
11
+ 'application/pdf',
12
+ 'image/heic',
13
+ 'image/png',
14
+ 'image/jpeg',
15
+ 'image/tiff',
16
+ 'image/webp',
17
+ ].freeze
18
+
19
+ # Base class for loading documents.
20
+ class InputDocument
21
+ # @return [String]
22
+ attr_reader :filename
23
+ # @return [String]
24
+ attr_reader :file_mimetype
25
+ # @return [StreamIO]
26
+ attr_reader :io_stream
27
+
28
+ # @param io_stream [StreamIO]
29
+ def initialize(io_stream, filename)
30
+ @io_stream = io_stream
31
+ @filename = filename
32
+ @file_mimetype = Marcel::MimeType.for @io_stream, name: @filename
33
+
34
+ return if ALLOWED_MIME_TYPES.include? @file_mimetype
35
+
36
+ raise "File type not allowed, must be one of #{ALLOWED_MIME_TYPES.join(', ')}"
37
+ end
38
+
39
+ def pdf?
40
+ @file_mimetype == 'application/pdf'
41
+ end
42
+
43
+ def process_pdf(options)
44
+ @io_stream.seek(0)
45
+ @io_stream = PdfProcessor.parse(@io_stream, options)
46
+ end
47
+
48
+ # @param close [Boolean]
49
+ def read_document(close: true)
50
+ @io_stream.seek(0)
51
+ data = @io_stream.read
52
+ @io_stream.close if close
53
+ [data].pack('m')
54
+ end
55
+ end
56
+
57
+ # Load a document from a path.
58
+ class PathDocument < InputDocument
59
+ # @param filepath [String]
60
+ def initialize(filepath)
61
+ io_stream = File.open(filepath, 'rb')
62
+ super(io_stream, File.basename(filepath))
63
+ end
64
+ end
65
+
66
+ # Load a document from a base64 string.
67
+ class Base64Document < InputDocument
68
+ # @param base64_string [String]
69
+ # @param filename [String]
70
+ def initialize(base64_string, filename)
71
+ io_stream = StringIO.new(base64_string.unpack1('m*'))
72
+ io_stream.set_encoding Encoding::BINARY
73
+ super(io_stream, filename)
74
+ end
75
+ end
76
+
77
+ # Load a document from raw bytes.
78
+ class BytesDocument < InputDocument
79
+ # @param raw_bytes [String]
80
+ # @param filename [String]
81
+ def initialize(raw_bytes, filename)
82
+ io_stream = StringIO.new(raw_bytes)
83
+ io_stream.set_encoding Encoding::BINARY
84
+ super(io_stream, filename)
85
+ end
86
+ end
87
+
88
+ # Load a document from a file handle.
89
+ class FileDocument < InputDocument
90
+ # @param filename [String]
91
+ def initialize(file_handle, filename)
92
+ io_stream = file_handle
93
+ super(io_stream, filename)
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'input/sources'
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'inference'
4
+
5
+ module Mindee
6
+ # Stores all response attributes.
7
+ class Document
8
+ # @return [Mindee::Inference]
9
+ attr_reader :inference
10
+ # @return [String] Filename sent to the API
11
+ attr_reader :name
12
+ # @return [String] Mindee ID of the document
13
+ attr_reader :id
14
+
15
+ # @param prediction_class [Class<Mindee::Prediction::Prediction>]
16
+ # @param http_response [Hash]
17
+ def initialize(prediction_class, http_response)
18
+ @id = http_response['id']
19
+ @name = http_response['name']
20
+ @inference = Mindee::Inference.new(prediction_class, http_response['inference'])
21
+ end
22
+
23
+ def to_s
24
+ out_str = String.new
25
+ out_str << "########\nDocument\n########"
26
+ out_str << "\n:Mindee ID: #{@id}"
27
+ out_str << "\n:Filename: #{@name}"
28
+ out_str << "\n\n#{@inference}"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mindee
4
+ module Parsing
5
+ # API Error
6
+ class Error < StandardError
7
+ # @return [String]
8
+ attr_reader :api_code
9
+ # @return [String]
10
+ attr_reader :api_details
11
+ # @return [String]
12
+ attr_reader :api_message
13
+
14
+ def initialize(error)
15
+ @api_code = error['code']
16
+ @api_details = error['details']
17
+ @api_message = error['message']
18
+ super("#{@api_code}: #{@api_details} - #{@api_message}")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'page'
4
+
5
+ module Mindee
6
+ # Product information
7
+ class Product
8
+ attr_reader :name, :type, :version
9
+
10
+ # @param http_response [Hash]
11
+ def initialize(http_response)
12
+ @name = http_response['name']
13
+ @type = http_response['type']
14
+ @version = http_response['version']
15
+ end
16
+ end
17
+
18
+ # Inference holds all predictions
19
+ class Inference
20
+ # @return [Boolean]
21
+ attr_reader :is_rotation_applied
22
+ # @return [Array<Mindee::Page>]
23
+ attr_reader :pages
24
+ # @return [Mindee::Prediction]
25
+ attr_reader :prediction
26
+ # @return [Mindee::Product]
27
+ attr_reader :product
28
+
29
+ # @param prediction_class [Class<Mindee::Prediction::Prediction>]
30
+ # @param http_response [Hash]
31
+ def initialize(prediction_class, http_response)
32
+ @is_rotation_applied = http_response['is_rotation_applied']
33
+ @prediction = prediction_class.new(http_response['prediction'], nil)
34
+ @product = Product.new(http_response['product'])
35
+ @pages = []
36
+ http_response['pages'].each do |page|
37
+ @pages.push(Page.new(prediction_class, page))
38
+ end
39
+ end
40
+
41
+ def to_s
42
+ is_rotation_applied = @is_rotation_applied ? 'Yes' : 'No'
43
+ out_str = String.new
44
+ out_str << "Inference\n#########"
45
+ out_str << "\n:Product: #{@product.name} v#{@product.version}"
46
+ out_str << "\n:Rotation applied: #{is_rotation_applied}"
47
+ out_str << "\n\nPrediction\n=========="
48
+ out_str << "\n#{@prediction}"
49
+ out_str << "\n\nPage Predictions\n================\n\n"
50
+ out_str << @pages.map(&:to_s).join("\n\n")
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mindee
4
+ # Page orientation
5
+ class Orientation
6
+ # @return [Integer]
7
+ attr_reader :page_id
8
+ # A prediction among these 3 possible outputs:
9
+ # * 0 degrees: the page is already upright
10
+ # * 90 degrees: the page must be rotated clockwise to be upright
11
+ # * 270 degrees: the page must be rotated counterclockwise to be upright
12
+ # @return [Integer, nil]
13
+ attr_reader :value
14
+
15
+ # @param prediction [Hash]
16
+ # @param page_id [Integer]
17
+ def initialize(prediction, page_id)
18
+ @value = prediction['value']
19
+ @page_id = page_id
20
+ end
21
+ end
22
+
23
+ # Page object
24
+ class Page
25
+ # @return [Integer]
26
+ attr_reader :id
27
+ # @return [Mindee::Prediction]
28
+ attr_reader :prediction
29
+ # @return [Mindee::Orientation]
30
+ attr_reader :orientation
31
+
32
+ def initialize(prediction_class, page)
33
+ @id = page['id'].to_i
34
+ @prediction = prediction_class.new(page['prediction'], @id)
35
+ @orientation = Orientation.new(page['orientation'], @id)
36
+ end
37
+
38
+ def to_s
39
+ out_str = String.new
40
+ title = "Page #{@id}"
41
+ out_str << "#{title}\n"
42
+ out_str << ('-' * title.size)
43
+ out_str << "\n#{@prediction}"
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mindee
4
+ module Prediction
5
+ # Base document object.
6
+ class Prediction
7
+ # document type
8
+ # @return [String]
9
+ attr_reader :document_type
10
+ # Validation checks for the document
11
+ # @return [Hash<Symbol, Boolean>]
12
+ attr_reader :checklist
13
+ # Original filename of the document
14
+ # @return [String, nil]
15
+ attr_reader :filename
16
+ # Detected MIME type of the document
17
+ # @return [String, nil]
18
+ attr_reader :file_mimetype
19
+
20
+ def initialize(_prediction, _page_id)
21
+ @checklist = {}
22
+ end
23
+
24
+ # @return [Boolean]
25
+ def all_checks
26
+ @checklist.all? { |_, value| value == true }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -4,7 +4,7 @@ require_relative 'base'
4
4
 
5
5
  module Mindee
6
6
  # Represents tax information.
7
- class Amount < Field
7
+ class AmountField < Field
8
8
  # Amount value as 3 decimal float
9
9
  # @return [Float, nil]
10
10
  attr_reader :value
@@ -13,5 +13,9 @@ module Mindee
13
13
  super
14
14
  @value = @value.round(3) unless @value.nil?
15
15
  end
16
+
17
+ def to_s
18
+ Field.float_to_string(@value)
19
+ end
16
20
  end
17
21
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../geometry'
3
+ require_relative '../../../geometry'
4
4
 
5
5
  module Mindee
6
6
  # Base field object.
@@ -8,8 +8,8 @@ module Mindee
8
8
  # @return [String, Float, Integer, Boolean]
9
9
  attr_reader :value
10
10
  # @return [Array<Array<Float>>]
11
- attr_reader :bbox
12
- # @return [Array<Array<Float>>]
11
+ attr_reader :bounding_box
12
+ # @return [Mindee::Geometry::Polygon]
13
13
  attr_reader :polygon
14
14
  # @return [Integer, nil]
15
15
  attr_reader :page_id
@@ -26,8 +26,8 @@ module Mindee
26
26
  def initialize(prediction, page_id, reconstructed: false)
27
27
  @value = prediction['value']
28
28
  @confidence = prediction['confidence']
29
- @polygon = prediction['polygon']
30
- @bbox = Geometry.get_bbox_as_polygon(@polygon) unless @polygon.nil? || @polygon.empty?
29
+ @polygon = Geometry.polygon_from_prediction(prediction['polygon'])
30
+ @bounding_box = Geometry.get_bounding_box(@polygon) unless @polygon.nil? || @polygon.empty?
31
31
  @page_id = page_id || prediction['page_id']
32
32
  @reconstructed = reconstructed
33
33
  end
@@ -57,5 +57,16 @@ module Mindee
57
57
  end
58
58
  arr_sum.to_f
59
59
  end
60
+
61
+ # @param value [Float]
62
+ # @param min_precision [Integer]
63
+ def self.float_to_string(value, min_precision = 2)
64
+ return String.new if value.nil?
65
+
66
+ precision = value.to_f.to_s.split('.')[1].size
67
+ precision = [precision, min_precision].max
68
+ format_string = "%.#{precision}f"
69
+ format(format_string, value)
70
+ end
60
71
  end
61
72
  end