openfoodfoundation/openfoodnetwork

View on GitHub
engines/dfc_provider/app/services/quantitative_value_builder.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

# Describes the quantity contained in a product, for example:
#
# - 1 piece of apple, usually meaning the whole fruit
# - 2 litres of milk, for example in a big bottle or pouch
# - 750 grams of bread, for example a loaf
#
# The DFC also supports specific units like loafs and cans but we don't have
# standardised data within OFN to map to these types.
class QuantitativeValueBuilder < DfcBuilder
  def self.quantity(variant)
    DataFoodConsortium::Connector::QuantitativeValue.new(
      unit: unit(variant.product.variant_unit),
      value: variant.unit_value,
    )
  end

  def self.unit(unit_name)
    case unit_name
    when "volume"
      DfcLoader.connector.MEASURES.LITRE
    when "weight"
      DfcLoader.connector.MEASURES.GRAM
    else
      DfcLoader.connector.MEASURES.PIECE
    end
  end

  def self.apply(quantity, product)
    measure, unit_name, unit_scale = map_unit(quantity.unit)

    product.variant_unit = measure
    product.variant_unit_name = unit_name if measure == "items"
    product.variant_unit_scale = unit_scale
    product.unit_value = quantity.value.to_f * unit_scale
  end

  # Map DFC units to OFN fields:
  #
  # - variant_unit
  # - variant_unit_name
  # - variant_unit_scale
  #
  # Unimplemented measures
  #
  # The DFC knows lots of single piece measures like a tub. There are not
  # listed here and automatically mapped to "item". The following is a list
  # of measures we want or could implement.
  #
  # Length is not represented in the OFN:
  #
  # :CENTIMETRE,
  # :DECIMETRE,
  # :METRE,
  # :KILOMETRE,
  # :INCH,
  #
  # Other:
  #
  # :PERCENT,
  #
  # This method is quite long and may be shortened with new DFC features:
  #
  # * https://github.com/datafoodconsortium/taxonomies/issues/7
  # * https://github.com/datafoodconsortium/connector-ruby/issues/18
  #
  # Until then, we can ignore Rubocop metrics, IMO.
  def self.map_unit(unit) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
    quantity_unit = DfcLoader.connector.MEASURES

    # The unit name is only set for items. The name is implied for weight and
    # volume and filled in by `WeightsAndMeasures`.
    case unit
    when quantity_unit.LITRE
      ["volume", nil, 1]
    when quantity_unit.MILLILITRE
      ["volume", nil, 0.001]
    when quantity_unit.CENTILITRE
      ["volume", nil, 0.01]
    when quantity_unit.DECILITRE
      ["volume", nil, 0.1]
    when quantity_unit.GALLON
      ["volume", nil, 4.54609]

    when quantity_unit.MILLIGRAM
      ["weight", nil, 0.001]
    when quantity_unit.GRAM
      ["weight", nil, 1]
    when quantity_unit.KILOGRAM
      ["weight", nil, 1_000]
    when quantity_unit.TONNE
      ["weight", nil, 1_000_000]
    # Not part of the DFC yet:
    # when quantity_unit.OUNCE
    #   ["weight", nil, 28.349523125]
    when quantity_unit.POUNDMASS
      ["weight", nil, 453.59237]

    when quantity_unit.PAIR
      ["items", "pair", 2]
    when quantity_unit._4PACK
      ["items", "4 pack", 4]
    when quantity_unit._6PACK
      ["items", "6 pack", 6]
    when quantity_unit.HALFDOZEN
      ["items", "half dozen", 6]
    when quantity_unit.DOZEN
      ["items", "dozen", 12]
    else
      # Labels may be provided one day:
      # https://github.com/datafoodconsortium/connector-ruby/issues/18
      unit_id = unit.try(:semanticId)&.split("#")&.last&.split(":")&.last
      label = unit_id || "items"
      ["items", label, 1]
    end
  end
end