Module: VisionAPI::Result

Defined in:
lib/vision_api/result.rb

Overview

Helpers for reading a result.

None of this is required — res["total"]["value"] is a perfectly good way to read a field, and responses stay plain hashes. These exist because three rules trip people up often enough to be worth a method:

  1. every scalar is wrapped in {"value", "confidence"};
  2. a preset response contains every field of the preset, absent ones as nil;
  3. a line-item array is a bare array whose cells are wrapped individually — the array itself has no confidence, each cell has its own.

Constant Summary collapse

ORDER =
{ "low" => 0, "mid" => 1, "high" => 2 }.freeze

Class Method Summary collapse

Class Method Details

.at_least?(node, level) ⇒ Boolean

Returns whether the field's confidence is at least level.

Returns:

  • (Boolean)

    whether the field's confidence is at least level



95
96
97
98
99
# File 'lib/vision_api/result.rb', line 95

def at_least?(node, level)
  return false unless field?(node)

  ORDER.fetch(node["confidence"], 0) >= ORDER.fetch(level.to_s)
end

.below_confidence(result, level) ⇒ Object

The scalar fields found but below level — your review queue.

Extract at the default min_confidence: "low" so nothing is silently dropped, then route what came back weak to a human instead of trusting it.



105
106
107
108
109
# File 'lib/vision_api/result.rb', line 105

def below_confidence(result, level)
  (result || {}).filter_map do |key, node|
    key if field?(node) && !node["value"].nil? && !at_least?(node, level)
  end
end

.field(result, name) ⇒ Hash?

Returns the wrapped scalar, or nil when the result has no such field.

Returns:

  • (Hash, nil)

    the wrapped scalar, or nil when the result has no such field



54
55
56
57
# File 'lib/vision_api/result.rb', line 54

def field(result, name)
  node = (result || {})[name.to_s]
  field?(node) ? node : nil
end

.field?(node) ⇒ Boolean

Returns whether the node is a wrapped scalar rather than rows or a block.

Returns:

  • (Boolean)

    whether the node is a wrapped scalar rather than rows or a block



20
21
22
# File 'lib/vision_api/result.rb', line 20

def field?(node)
  node.is_a?(Hash) && node.key?("value") && node.key?("confidence")
end

.missing(result) ⇒ Array<String>

Returns the fields that came back empty.

Returns:

  • (Array<String>)

    the fields that came back empty



89
90
91
92
# File 'lib/vision_api/result.rb', line 89

def missing(result)
  found = present(result)
  (result || {}).keys.reject { |key| found.include?(key) }
end

.present(result) ⇒ Array<String>

Returns the fields the document actually carried.

Returns:

  • (Array<String>)

    the fields the document actually carried



76
77
78
79
80
81
82
83
84
85
86
# File 'lib/vision_api/result.rb', line 76

def present(result)
  (result || {}).filter_map do |key, node|
    if rows?(node)
      key unless node.empty?
    elsif field?(node)
      key unless node["value"].nil?
    else
      key
    end
  end
end

.rows(result, name) ⇒ Array<Hash>

Returns a line-item array; [] when absent or the document had no lines.

Returns:

  • (Array<Hash>)

    a line-item array; [] when absent or the document had no lines



70
71
72
73
# File 'lib/vision_api/result.rb', line 70

def rows(result, name)
  node = (result || {})[name.to_s]
  rows?(node) ? node : []
end

.rows?(node) ⇒ Boolean

Returns whether the node is a line-item / repeated-block array.

Returns:

  • (Boolean)

    whether the node is a line-item / repeated-block array



25
26
27
# File 'lib/vision_api/result.rb', line 25

def rows?(node)
  node.is_a?(Array)
end

.unwrap(result, drop_null: false) ⇒ Hash

Drops the wrappers, recursively.

unwrap("total" => {"value" => 12, "confidence" => "high"})  # => {"total" => 12}

Absent fields stay as nil, because "the preset has this field and the document did not carry it" is information. Pass drop_null: true for only what was found.

Parameters:

  • result (Hash, nil)

Returns:

  • (Hash)


38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/vision_api/result.rb', line 38

def unwrap(result, drop_null: false)
  (result || {}).each_with_object({}) do |(key, node), out|
    if rows?(node)
      unwrapped = node.map { |row| unwrap(row, drop_null: drop_null) }
      out[key] = unwrapped unless drop_null && unwrapped.empty?
    elsif field?(node)
      next if drop_null && node["value"].nil?

      out[key] = node["value"]
    elsif node.is_a?(Hash)
      out[key] = unwrap(node, drop_null: drop_null)
    end
  end
end

.value(result, name, default = nil) ⇒ Object

A scalar field's value directly.

Returns default when the field is absent from the schema or present with a null value — collapsing the two on purpose, for the common case where you only want the number.



64
65
66
67
# File 'lib/vision_api/result.rb', line 64

def value(result, name, default = nil)
  node = field(result, name)
  node.nil? || node["value"].nil? ? default : node["value"]
end