Merge pull request #11377 from mkllnk/dfc-update-request

[DFC API] Import known units when creating new products
This commit is contained in:
Maikel
2023-09-08 09:05:26 +10:00
committed by GitHub
10 changed files with 343 additions and 122 deletions

View File

@@ -24,16 +24,24 @@ class WeightsAndMeasures
UNITS = {
'weight' => {
0.001 => { 'name' => 'mg', 'system' => 'metric' },
1.0 => { 'name' => 'g', 'system' => 'metric' },
28.35 => { 'name' => 'oz', 'system' => 'imperial' },
453.6 => { 'name' => 'lb', 'system' => 'imperial' },
1000.0 => { 'name' => 'kg', 'system' => 'metric' },
1_000_000.0 => { 'name' => 'T', 'system' => 'metric' }
1_000_000.0 => { 'name' => 'T', 'system' => 'metric' },
28.349523125 => { 'name' => 'oz', 'system' => 'imperial' },
28.35 => { 'name' => 'oz', 'system' => 'imperial' },
453.59237 => { 'name' => 'lb', 'system' => 'imperial' },
453.6 => { 'name' => 'lb', 'system' => 'imperial' },
},
'volume' => {
0.001 => { 'name' => 'mL', 'system' => 'metric' },
0.01 => { 'name' => 'cL', 'system' => 'metric' },
0.1 => { 'name' => 'dL', 'system' => 'metric' },
1.0 => { 'name' => 'L', 'system' => 'metric' },
1000.0 => { 'name' => 'kL', 'system' => 'metric' }
1000.0 => { 'name' => 'kL', 'system' => 'metric' },
4.54609 => { 'name' => 'gal', 'system' => 'imperial' },
}
}.freeze

View File

@@ -30,17 +30,14 @@ module DfcProvider
end
def update
dfc_request = JSON.parse(request.body.read)
return unless dfc_request.key?("dfc-b:description")
supplied_product = import&.first
variant.product.update!(
description: dfc_request["dfc-b:description"],
)
return head :bad_request unless supplied_product
# This input is DFC v1.6 currently sent by the DFC Prototype.
variant.update!(
unit_value: dfc_request["dfc-b:quantity"],
)
SuppliedProductBuilder.apply(supplied_product, variant)
variant.product.save!
variant.save!
end
private

View File

@@ -28,17 +28,90 @@ class QuantitativeValueBuilder < DfcBuilder
end
def self.apply(quantity, product)
product.variant_unit, product.variant_unit_name =
case quantity.unit
when DfcLoader.connector.MEASURES.UNIT.QUANTITYUNIT.LITRE
["volume", "liter"]
when DfcLoader.connector.MEASURES.UNIT.QUANTITYUNIT.GRAM
["weight", "gram"]
else
["items", "items"]
end
measure, unit_name, unit_scale = map_unit(quantity.unit)
product.variant_unit_scale = 1
product.unit_value = quantity.value
product.variant_unit = measure
product.variant_unit_name = unit_name if measure == "items"
product.variant_unit_scale = unit_scale
product.unit_value = quantity.value * 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.UNIT.QUANTITYUNIT
# 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
label = unit.try(:semanticId)&.split("#")&.last || "items"
["items", label, 1]
end
end
end

View File

@@ -26,4 +26,14 @@ class SuppliedProductBuilder < DfcBuilder
QuantitativeValueBuilder.apply(supplied_product.quantity, product)
end
end
def self.apply(supplied_product, variant)
variant.product.assign_attributes(
name: supplied_product.name,
description: supplied_product.description,
)
QuantitativeValueBuilder.apply(supplied_product.quantity, variant.product)
variant.unit_value = variant.product.unit_value
end
end

View File

@@ -1,85 +0,0 @@
{
"@context": {
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
"skos": "http://www.w3.org/2004/02/skos/core#",
"dfc": "http://static.datafoodconsortium.org/ontologies/DFC_FullModel.owl#",
"dc": "http://purl.org/dc/elements/1.1/#",
"dfc-b": "http://static.datafoodconsortium.org/ontologies/DFC_BusinessOntology.owl#",
"dfc-p": "http://static.datafoodconsortium.org/ontologies/DFC_ProductOntology.owl#",
"dfc-t": "http://static.datafoodconsortium.org/ontologies/DFC_TechnicalOntology.owl#",
"dfc-m": "http://static.datafoodconsortium.org/data/measures.rdf#",
"dfc-pt": "http://static.datafoodconsortium.org/data/productTypes.rdf#",
"dfc-f": "http://static.datafoodconsortium.org/data/facets.rdf#",
"dfc-p:hasUnit": {
"@type": "@id"
},
"dfc-b:hasUnit": {
"@type": "@id"
},
"dfc-b:hasQuantity": {
"@type": "@id"
},
"dfc-p:hasType": {
"@type": "@id"
},
"dfc-b:hasType": {
"@type": "@id"
},
"dfc-b:references": {
"@type": "@id"
},
"dfc-b:referencedBy": {
"@type": "@id"
},
"dfc-b:offeres": {
"@type": "@id"
},
"dfc-b:supplies": {
"@type": "@id"
},
"dfc-b:defines": {
"@type": "@id"
},
"dfc-b:affiliates": {
"@type": "@id"
},
"dfc-b:manages": {
"@type": "@id"
},
"dfc-b:offeredThrough": {
"@type": "@id"
},
"dfc-b:hasBrand": {
"@type": "@id"
},
"dfc-b:hasGeographicalOrigin": {
"@type": "@id"
},
"dfc-b:hasClaim": {
"@type": "@id"
},
"dfc-b:hasAllergenDimension": {
"@type": "@id"
},
"dfc-b:hasNutrimentDimension": {
"@type": "@id"
},
"dfc-b:hasPhysicalDimension": {
"@type": "@id"
},
"dfc:owner": {
"@type": "@id"
},
"dfc-t:hostedBy": {
"@type": "@id"
},
"dfc-t:hasPivot": {
"@type": "@id"
},
"dfc-t:represent": {
"@type": "@id"
}
},
"dfc-b:description": "DFC-Pesto updated",
"dfc-b:quantity": 17
}

View File

@@ -0,0 +1,105 @@
{
"@context": {
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
"skos": "http://www.w3.org/2004/02/skos/core#",
"dfc": "https://github.com/datafoodconsortium/ontology/releases/latest/download/DFC_FullModel.owl#",
"dc": "http://purl.org/dc/elements/1.1/#",
"dfc-b": "https://github.com/datafoodconsortium/ontology/releases/latest/download/DFC_BusinessOntology.owl#",
"dfc-p": "https://github.com/datafoodconsortium/ontology/releases/latest/download/DFC_ProductGlossary.owl#",
"dfc-t": "https://github.com/datafoodconsortium/ontology/releases/latest/download/DFC_TechnicalOntology.owl#",
"dfc-m": "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/measures.rdf#",
"dfc-pt": "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/productTypes.rdf#",
"dfc-f": "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/facets.rdf#",
"ontosec": "http://www.semanticweb.org/ontologies/2008/11/OntologySecurity.owl#",
"dfc-p:hasUnit": {
"@type": "@id"
},
"dfc-b:hasUnit": {
"@type": "@id"
},
"dfc-b:hasQuantity": {
"@type": "@id"
},
"dfc-p:hasType": {
"@type": "@id"
},
"dfc-b:hasType": {
"@type": "@id"
},
"dfc-b:references": {
"@type": "@id"
},
"dfc-b:referencedBy": {
"@type": "@id"
},
"dfc-b:offeres": {
"@type": "@id"
},
"dfc-b:supplies": {
"@type": "@id"
},
"dfc-b:defines": {
"@type": "@id"
},
"dfc-b:affiliates": {
"@type": "@id"
},
"dfc-b:hasCertification": {
"@type": "@id"
},
"dfc-b:manages": {
"@type": "@id"
},
"dfc-b:offeredThrough": {
"@type": "@id"
},
"dfc-b:hasBrand": {
"@type": "@id"
},
"dfc-b:hasGeographicalOrigin": {
"@type": "@id"
},
"dfc-b:hasClaim": {
"@type": "@id"
},
"dfc-b:hasAllergenDimension": {
"@type": "@id"
},
"dfc-b:hasNutrientDimension": {
"@type": "@id"
},
"dfc-b:hasPhysicalDimension": {
"@type": "@id"
},
"dfc:owner": {
"@type": "@id"
},
"dfc-t:hostedBy": {
"@type": "@id"
},
"dfc-t:hasPivot": {
"@type": "@id"
},
"dfc-t:represent": {
"@type": "@id"
}
},
"@id": "https://staging.coopcircuits.fr/api/dfc-v1.7/enterprises/2731/supplied_products/56790",
"@type": "dfc-b:SuppliedProduct",
"dfc-b:alcoholPercentage": 0,
"dfc-b:description": "DFC-Pesto updated",
"dfc-b:hasQuantity": {
"@type": "dfc-b:QuantitativeValue",
"dfc-b:hasUnit": "dfc-m:Piece",
"dfc-b:value": 17
},
"dfc-b:hasType": "dfc-pt:non-local-vegetable",
"dfc-b:lifetime": "",
"dfc-b:name": "Pesto novo",
"dfc-b:totalTheoreticalStock": 0,
"dfc-b:usageOrStorageCondition": "",
"dfc:owner": "http://proto.datafoodconsortium.org:3000/ldp/user/64c1d30351ecb4367037a9f6",
"dfc-b:hasPhysicalCharacteristic": [],
"dfc-b:hasNutrientCharacteristic": [],
"dfc-b:hasAllergenCharacteristic": []
}

View File

@@ -32,6 +32,9 @@ describe "SuppliedProducts", type: :request, swagger_doc: "dfc-v1.7/swagger.yaml
'dfc-b': "http://static.datafoodconsortium.org/ontologies/DFC_BusinessOntology.owl#",
'dfc-m': "http://static.datafoodconsortium.org/data/measures.rdf#",
'dfc-pt': "http://static.datafoodconsortium.org/data/productTypes.rdf#",
'dfc-b:hasUnit': {
'@type': "@id"
},
},
'@id': "http://test.host/api/dfc-v1.7/enterprises/6201/supplied_products/0",
'@type': "dfc-b:SuppliedProduct",
@@ -128,7 +131,7 @@ describe "SuppliedProducts", type: :request, swagger_doc: "dfc-v1.7/swagger.yaml
consumes "application/json"
parameter name: :supplied_product, in: :body, schema: {
example: ExampleJson.read("patch_supplied_product")
example: ExampleJson.read("put_supplied_product")
}
let(:id) { variant.id }
@@ -148,6 +151,7 @@ describe "SuppliedProducts", type: :request, swagger_doc: "dfc-v1.7/swagger.yaml
submit_request(example.metadata)
variant.reload
}.to change { variant.description }.to("DFC-Pesto updated")
.and change { variant.name }.to("Pesto novo")
.and change { variant.unit_value }.to(17)
end
end

View File

@@ -43,4 +43,93 @@ describe QuantitativeValueBuilder do
expect(quantity.unit.semanticId).to eq "dfc-m:Piece"
end
end
describe ".apply" do
let(:quantity_unit) { DfcLoader.connector.MEASURES.UNIT.QUANTITYUNIT }
let(:product) { Spree::Product.new }
it "uses items for anything unknown" do
quantity = DataFoodConsortium::Connector::QuantitativeValue.new(
unit: quantity_unit.JAR,
value: 3,
)
builder.apply(quantity, product)
expect(product.variant_unit).to eq "items"
expect(product.variant_unit_name).to eq "Jar"
expect(product.variant_unit_scale).to eq 1
expect(product.unit_value).to eq 3
end
it "knows metric units" do
quantity = DataFoodConsortium::Connector::QuantitativeValue.new(
unit: quantity_unit.LITRE,
value: 2,
)
builder.apply(quantity, product)
expect(product.variant_unit).to eq "volume"
expect(product.variant_unit_name).to eq nil
expect(product.variant_unit_scale).to eq 1
expect(product.unit_value).to eq 2
end
it "knows metric units with a scale in OFN" do
quantity = DataFoodConsortium::Connector::QuantitativeValue.new(
unit: quantity_unit.KILOGRAM,
value: 4,
)
builder.apply(quantity, product)
expect(product.variant_unit).to eq "weight"
expect(product.variant_unit_name).to eq nil
expect(product.variant_unit_scale).to eq 1_000
expect(product.unit_value).to eq 4_000
end
it "knows metric units with a small scale" do
quantity = DataFoodConsortium::Connector::QuantitativeValue.new(
unit: quantity_unit.MILLIGRAM,
value: 5,
)
builder.apply(quantity, product)
expect(product.variant_unit).to eq "weight"
expect(product.variant_unit_name).to eq nil
expect(product.variant_unit_scale).to eq 0.001
expect(product.unit_value).to eq 0.005
end
it "knows imperial units" do
quantity = DataFoodConsortium::Connector::QuantitativeValue.new(
unit: quantity_unit.POUNDMASS,
value: 10,
)
builder.apply(quantity, product)
expect(product.variant_unit).to eq "weight"
expect(product.variant_unit_name).to eq nil
expect(product.variant_unit_scale).to eq 453.59237
expect(product.unit_value).to eq 4_535.9237
end
it "knows customary units" do
quantity = DataFoodConsortium::Connector::QuantitativeValue.new(
unit: quantity_unit.DOZEN,
value: 2,
)
builder.apply(quantity, product)
expect(product.variant_unit).to eq "items"
expect(product.variant_unit_name).to eq "dozen"
expect(product.variant_unit_scale).to eq 12
expect(product.unit_value).to eq 24
end
end
end

View File

@@ -115,8 +115,8 @@ module VariantUnits
p = double(:product, variant_unit: 'volume', variant_unit_scale: scale)
allow(v).to receive(:product) { p }
allow(p).to receive(:persisted?) { true }
allow(v).to receive(:unit_value) { 100 * scale }
expect(subject.send(:option_value_value_unit)).to eq [100, unit]
allow(v).to receive(:unit_value) { 3 * scale }
expect(subject.send(:option_value_value_unit)).to eq [3, unit]
end
end

View File

@@ -330,7 +330,7 @@ paths:
dfc-b:hasType: http://static.datafoodconsortium.org/data/productTypes.rdf#non-local-vegetable
dfc-b:hasQuantity:
"@type": dfc-b:QuantitativeValue
dfc-b:hasUnit: dfc-m:Piece
dfc-b:hasUnit: dfc-m:Gram
dfc-b:value: 3.0
dfc-b:alcoholPercentage: 0.0
dfc-b:lifetime: ''
@@ -345,6 +345,8 @@ paths:
dfc-b: http://static.datafoodconsortium.org/ontologies/DFC_BusinessOntology.owl#
dfc-m: http://static.datafoodconsortium.org/data/measures.rdf#
dfc-pt: http://static.datafoodconsortium.org/data/productTypes.rdf#
dfc-b:hasUnit:
"@type": "@id"
"@id": http://test.host/api/dfc-v1.7/enterprises/6201/supplied_products/0
"@type": dfc-b:SuppliedProduct
dfc-b:name: Apple
@@ -412,14 +414,15 @@ paths:
"@context":
rdfs: http://www.w3.org/2000/01/rdf-schema#
skos: http://www.w3.org/2004/02/skos/core#
dfc: http://static.datafoodconsortium.org/ontologies/DFC_FullModel.owl#
dfc: https://github.com/datafoodconsortium/ontology/releases/latest/download/DFC_FullModel.owl#
dc: http://purl.org/dc/elements/1.1/#
dfc-b: http://static.datafoodconsortium.org/ontologies/DFC_BusinessOntology.owl#
dfc-p: http://static.datafoodconsortium.org/ontologies/DFC_ProductOntology.owl#
dfc-t: http://static.datafoodconsortium.org/ontologies/DFC_TechnicalOntology.owl#
dfc-m: http://static.datafoodconsortium.org/data/measures.rdf#
dfc-pt: http://static.datafoodconsortium.org/data/productTypes.rdf#
dfc-f: http://static.datafoodconsortium.org/data/facets.rdf#
dfc-b: https://github.com/datafoodconsortium/ontology/releases/latest/download/DFC_BusinessOntology.owl#
dfc-p: https://github.com/datafoodconsortium/ontology/releases/latest/download/DFC_ProductGlossary.owl#
dfc-t: https://github.com/datafoodconsortium/ontology/releases/latest/download/DFC_TechnicalOntology.owl#
dfc-m: https://github.com/datafoodconsortium/taxonomies/releases/latest/download/measures.rdf#
dfc-pt: https://github.com/datafoodconsortium/taxonomies/releases/latest/download/productTypes.rdf#
dfc-f: https://github.com/datafoodconsortium/taxonomies/releases/latest/download/facets.rdf#
ontosec: http://www.semanticweb.org/ontologies/2008/11/OntologySecurity.owl#
dfc-p:hasUnit:
"@type": "@id"
dfc-b:hasUnit:
@@ -442,6 +445,8 @@ paths:
"@type": "@id"
dfc-b:affiliates:
"@type": "@id"
dfc-b:hasCertification:
"@type": "@id"
dfc-b:manages:
"@type": "@id"
dfc-b:offeredThrough:
@@ -454,7 +459,7 @@ paths:
"@type": "@id"
dfc-b:hasAllergenDimension:
"@type": "@id"
dfc-b:hasNutrimentDimension:
dfc-b:hasNutrientDimension:
"@type": "@id"
dfc-b:hasPhysicalDimension:
"@type": "@id"
@@ -466,7 +471,22 @@ paths:
"@type": "@id"
dfc-t:represent:
"@type": "@id"
"@id": https://staging.coopcircuits.fr/api/dfc-v1.7/enterprises/2731/supplied_products/56790
"@type": dfc-b:SuppliedProduct
dfc-b:alcoholPercentage: 0
dfc-b:description: DFC-Pesto updated
dfc-b:quantity: 17
dfc-b:hasQuantity:
"@type": dfc-b:QuantitativeValue
dfc-b:hasUnit: dfc-m:Piece
dfc-b:value: 17
dfc-b:hasType: dfc-pt:non-local-vegetable
dfc-b:lifetime: ''
dfc-b:name: Pesto novo
dfc-b:totalTheoreticalStock: 0
dfc-b:usageOrStorageCondition: ''
dfc:owner: http://proto.datafoodconsortium.org:3000/ldp/user/64c1d30351ecb4367037a9f6
dfc-b:hasPhysicalCharacteristic: []
dfc-b:hasNutrientCharacteristic: []
dfc-b:hasAllergenCharacteristic: []
servers:
- url: "/"