Thngs, Collections, and Actions

This is the key section for setting up the supply chain products and getting data from them into the EVRYTHNG Platform. The Thngs represent the bottles, collection resources represent cases of bottles, and actions of various types record progress from factory to store as a series of discrete events.


Create Some Thngs

Each bottle of beer manufactured in the factory is given its own Thng resource, which is the principle data type within the EVRYTHNG Platform. Thngs represent single instances of an object class and are created with a link to the product resource representing their SKU. Create your first Thng which references the “Honeysuckle Zombie Brown Ale” product you created earlier by making a POST /thngs request.

Like the product creation request, this one must be scoped to the correct project by using the project query parameter. This time, the request must be made with the Application User API Key, because the user associated with the key was the original creator of the bottle.

Substitutions: :projectId, :productId

curl -H "Content-Type: application/json" \
  -H "Authorization: $APPLICATION_USER_API_KEY" \
  -X POST 'https://api.evrythng.com/thngs?project=:projectId' \
  -d '{
    "name": "HZBA #43897",
    "description": "A single bottle of Honeysuckle Zombie Brown Ale",
    "product": ":productId"
  }'
HTTP/1.1 201 Created
Content-Type: application/json

{
  "id": "UmFSm6PkVDPw9pRwRECx4cVa",
  "createdAt": 1501666536785,
  "updatedAt": 1501666536785,
  "name": "HZBA #43897",
  "description": "A single bottle of Honeysuckle Zombie Brown Ale",
  "product": "U3EtU2k3BD8wQpwwR6EMXgKb"
}

Finally, create a few more Thngs for bottles of beer to model a case, such as a case of six bottles. Increment the number in each name value to make them unique.


Create a Case of Beer

Now that you've created multiple Thngs, the next step is to model a grouping of Thngs into a collection resource. The collection represents a case of beer that is shipped from the factory to the store. First, create a collection resource in the context of the project as the Application User by using a POST /collections request:

Substitutions: :projectId

curl -H "Content-Type: application/json" \
  -H "Authorization: $APPLICATION_USER_API_KEY" \
  -X POST 'https://api.evrythng.com/collections?project=:projectId' \
  -d '{
    "name": "HZBA Case #7316",
    "description": "A case of six HZBA beer bottles"
  }'
HTTP/1.1 201 Created
Content-Type: application/json

{
  "id": "UG2SGPSbqm8hhqRwwhaNMqke",
  "createdAt": 1501666833381,
  "updatedAt": 1501666833381,
  "name": "HZBA Case #7316",
  "description": "A case of six HZBA beer bottles"
}

Next, add the Thngs representing the individual bottles to the collection — the grouping of bottles inside the case. This is done by making a POST /collections/:collectionId/thngs request in which you supply the IDs of the Thngs to add in the request body.

Substitutions: :collectionId, multiple :thngIds

curl -H "Content-Type: application/json" \
  -H "Authorization: $APPLICATION_USER_API_KEY" \
  -X PUT 'https://api.evrythng.com/collections/:collectionId/thngs' \
  -d '[
    ":thng1Id", ":thng2Id", ":thng3Id", 
    ":thng4Id", ":thng5Id", ":thng6Id"
  ]'
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": "UG2SGPSbqm8hhqRwwhaNMqke",
  "createdAt": 1501666833381,
  "scopes": {
    "users": [
      "U3EQA4aDeXPwQpwRagPwMMhb"
    ],
    "projects": [
      "UGhQAe3eeXswQ5wwagWUNKaq"
    ]
  },
  "updatedAt": 1501666833381,
  "name": "HZBA Case #7316",
  "description": "A case of six HZBA beer bottles"
}

You can verify that that Thngs were added correctly using a GET /collections/:collectionId/thngs request.

Substitutions: :collectionId

curl -H "Authorization: $APPLICATION_USER_API_KEY" \
   -X GET 'https://api.evrythng.com/collections/:collectionId/thngs'

Each Thng now lists the collection ID as an item in its collections field, indicating the list of collections that Thng belongs to. (Note that Thngs can belong to multiple collections.) The response has been edited here for brevity.

HTTP/1.1 200 OK
Content-Type: application/json

[
  {
    "id": "Um2bmrby6GshhqRwwhManKgd",
    "name": "HZBA #43902",
    "product": "U3EtU2k3BD8wQpwwR6EMXgKb",
    "collections": [ "UG2SGPSbqm8hhqRwwhaNMqke" ]
  },
  {
    "id": "UmFx37wreMPN9NaawXeFXcMq",
    "name": "HZBA #43901",
    "product": "U3EtU2k3BD8wQpwwR6EMXgKb",
    "collections": [ "UG2SGPSbqm8hhqRwwhaNMqke" ]
  },
  {
    "id": "U3FbmrRXqQteEPRawDbyVnyk",
    "name": "HZBA #43900",
    "product": "U3EtU2k3BD8wQpwwR6EMXgKb",
    "collections": [ "UG2SGPSbqm8hhqRwwhaNMqke" ]
  },
  {
    "id": "UGkb3MQtVXsatpwawDRe3b6g",
    "name": "HZBA #43899",
    "product": "U3EtU2k3BD8wQpwwR6EMXgKb",
    "collections": [ "UG2SGPSbqm8hhqRwwhaNMqke" ]
  },
  {
    "id": "Um2SmMtmVgsaQpaaRhfx4gHa",
    "name": "HZBA #43898",
    "product": "U3EtU2k3BD8wQpwwR6EMXgKb",
    "collections": [ "UG2SGPSbqm8hhqRwwhaNMqke" ]
  },
  {
    "id": "UmFSm6PkVDPw9pRwRECx4cVa",
    "name": "HZBA #43897",
    "product": "U3EtU2k3BD8wQpwwR6EMXgKb",
    "collections": [ "UG2SGPSbqm8hhqRwwhaNMqke" ]
  }
]

Create Actions

With the data model detailing the products being shipped, we can begin to model their movements from factory to shop floor.

Actions are the Platform resource models for simple notifications and provide a lasting record of discrete events that can occur. They can also contain custom data fields to drive business logic. In our supply chain scenario, an action is created in a variety of situations to provide a history of locations, inventory scans, and other activities it's involved in.

Each action must have a type to describe its class. This is similar to the relationship between Thngs and products, but it's mandatory here. There are also four built-in action types, which you can view by making a GET /actions request using the Operator API Key.

Create new action types to represent the case’s movements at various stages of the supply chain. You can draw inspiration for the names of these new action types from the GS1 Core Business Vocabulary Standard (CBVS). When creating new action types, the name must start with an underscore. The first action type is _Active, which indicates an object has been introduced into the supply chain.

Substitutions: :projectId

curl -H "Content-Type: application/json" \
  -H "Authorization: $OPERATOR_API_KEY" \
  -X POST 'https://api.evrythng.com/actions?project=:projectId' \
  -d '{
    "name": "_Active"
  }'
HTTP/1.1 201 Created
Content-Type: application/json

{
  "id": "UGFbsEH5qm8EEMwawDNnBk5s",
  "createdAt": 1501685675342,
  "updatedAt": 1501685675342,
  "name": "_Active"
}

Now that the action type is in place, it can be used to create actions of that type. For the collection representing the case of beer, the first action in its simulated life cycle will be of this newly created _Action type. The action itself is created with the Application User’s API Key to represent the user who created the case. Perform a GET /collections request if you don't know the case’s ID.

Substitutions: :projectId, :collectionId

curl -H "Content-Type: application/json" \
  -H "Authorization: $APPLICATION_USER_API_KEY" \
  -X POST 'https://api.evrythng.com/actions/_Active?project=:projectId' \
  -d '{
    "type": "_Active",
    "collection": ":collectionId"
  }'
HTTP/1.1 201 Created
Content-Type: application/json

{
  "id": "UGkSQwKcWc9n9Qwawg7Nmh7e",
  "createdAt": 1501687986689,
  "timestamp": 1501687986689,
  "type": "_Active",
  "user": "U3EQA4aDeXPwQpwRagPwMMhb",
  "location": {
    "latitude": 51.45,
    "longitude": 0.2167,
    "position": {
      "type": "Point",
      "coordinates": [
        0.2167,
        51.45
      ]
    }
  },
  "locationSource": "geoIp",
  "context": {
    "city": "Dartford",
    "region": "England",
    "countryCode": "GB",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
    "timeZone": "Europe/London"
  },
  "createdByProject": "UGhQAe3eeXswQ5wwagWUNKaq",
  "createdByApp": "UGE9dWaxBXsRtKRwaDdWnepa",
  "collection": "UG2SGPSbqm8hhqRwwhaNMqke"
}

And so the case has begun its journey. In a real integration, supply chain staff members would use the mobile app to scan and track the case as it enters and leaves warehouses, ships on trucks and freight trains, and finally arrives on the shop floor. These activities can be represented by the additional action types:

  • _In_Transit - Item is being shipped between two location.
  • _Encoded - An instance-level identifier has been written to a barcode or RFID (radio frequency identification) tag.
  • _Container_Closed - Item has been loaded onto a container.
  • _Sellable_Accessible - Item can be sold as-is to an end customer.
  • _Expired - Item is past its expiration date.
  • _Destroyed - Item has been fully rendered unusable.

The diagram below illustrates when each of these action types could occur in a real-life supply chain scenario. At each stage, the actions themselves contain additional data in their customFields property about the case’s current location, which can be used to build a map of the case’s journey across the world.

717717

Example Implementation

The Python script below demonstrates the creation of all these actions with historical timestamps to illustrate one case’s entire journey. After each action, the timestamp is wound back to the previous step.

You can run this script yourself to see it in action, but you need to replace the Application User API Key and place resource IDs with those you have created thus far.

import requests
import time

APPLICATION_USER_API_KEY = '1FGqgM8xr13gg4Ay9lukS4H3EwK1dP1x2ds67s7d...'
PROJECT_ID = 'UGhQAe3eeXswQ5wwagWUNKaq'
COLLECTION_ID = 'UGFkECmDVD8wQKaaaEgF4Een'

# Place IDs
FACTORY = 'UmkFemTdVXPa9Kwwwg4Nqm4q'
WAREHOUSE = 'Um2FVGQGegsat5waagH7QtGg'
SHIPPING_CENTER = 'UGFFV4Gqeg8a9KwRwYKcxfCq'
DISTRIBUTION_CENTER = 'UGFFBK8cBD8RtKRRwESbXsNh'
RETAILER_WAREHOUSE = 'U3F2BMCTegsRQpwwwkAdTnGm'
RETAILER_STORE = 'UG2Fe6PSBD8RtpwwwFdEecRt'
DISPOSAL_CENTER = 'UG22BNfWegsRQpRRwE3Qqr5a'

g_timestamp = int(round(time.time() * 1000))

def create_action(action_type, place_id):
  action = {
    'type': action_type,
    'collection': COLLECTION_ID,
    'locationSource': 'place', 
    'location': { 'place': place_id },
    'timestamp': g_timestamp
  }
  headers = {
    'Content-Type': 'application/json',
    'Authorization': APPLICATION_USER_API_KEY
  }
  url = 'https://api.evrythng.com/actions/{}?project={}'.format(action_type, PROJECT_ID)
  print(requests.post(url, json=action, headers=headers).text)

def subtract_days(days):
  global g_timestamp
  g_timestamp -= (1000 * 60 * 60 * 24 * days)

def create_supply_chain_history():
  create_action('_Destroyed', DISPOSAL_CENTER)
  subtract_days(1)
  create_action('_Expired', RETAILER_STORE)
  subtract_days(14)
  create_action('_Sellable_Accessible', RETAILER_STORE)
  subtract_days(1)
  create_action('_In_Transit', RETAILER_WAREHOUSE)
  subtract_days(4)
  create_action('_In_Transit', DISTRIBUTION_CENTER)
  subtract_days(2)
  create_action('_In_Transit', SHIPPING_CENTER)
  subtract_days(1)
  create_action('_Container_Closed', WAREHOUSE)
  subtract_days(1)
  create_action('_Encoded', WAREHOUSE)
  subtract_days(1)
  create_action('_In_Transit', FACTORY)
  subtract_days(1)
  create_action('_Active', FACTORY)

if __name__ == '__main__': 
  create_supply_chain_history()

In the real world, staff could create each of these actions in the inventory management mobile app whenever they see a case enter or leave a location, notice that it's expired, or when it's been destroyed. This could be done by manual entry or by using the Identifier Recognition feature of the EVRYTHNG Platform to scan each case for its collection ID and then create the actions with a Reactor script.

In the next section, you'll explore two other more major features of the Platform that can enhance the tagged products use-case: roles and permissions, and data visualization on the EVRYTHNG Dashboard.