Thngs, Collections, and Actions

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


## Creating Some Thngs

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

Like the product creation request, this should also be scoped to the correct project using the project query parameter. However, this time the request should be made with the Application User API Key, since the user associated with the key will be the original creator of the bottle themselves in a manufacturing capacity.

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 in order to model a case, such as a case of six bottles. Increment the number in each name value to tell them apart.


Creating a Case of Beer

Now that there are multiple Thngs, the next step in this scenario is to model a grouping of Thngs in a collection resource to represent a case of beer that will be shipped from the factory to the store. To do this, first create a collection resource in the context of the project as the Application User with 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"
}

With the collection in place, the next step is to add the Thngs representing the individual bottles to it, representing the grouping of the bottles inside the case. This is done by making a POST /collections/:collectionId/thngs request and supplying the IDs of the Thngs to add in the request payload.

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'

As you can see, each Thng now lists the collection ID as an item in its collections field, indicating the list of collections that Thng belongs to. 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" ]
  }
]

Creating Actions

With the data model detailing the products being shipped in this scenario set up, we can now begin to model their movements from factory to shop floor.

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

Each action created must have a type to describe its class. This is a similar relationship of Thngs and products, but the difference is that it is 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.

For this scenario, we will create new action types to represent the case’s movements at various stages of the supply chain. We can draw inspiration for the names of these new action types from the GS1 Core Business Vocabulary Standard (CBVS). When creating new action types, it is a requirement that the name starts with an underscore. The first action type we will use will be _Active which denotes that an object has been introduced to 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 will be created with the Application User’s API Key to represent the user who created the case. Perform a GET /collections request if you do not 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, the mobile app used by various members of supply chain staff would scan and track the case as it enters and leaves warehouses, ships on trucks and freight trains, and finally arrives at the shop floor. This 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 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 will 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.

## Example Implementation

The Python script below demonstrates the creation of all these actions with historical timestamps to mock 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 will need to replace the Application User API Key and place resource IDs with those you have created in the walkthrough 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, each of these actions could be created by the inventory management mobile app by staff whenever they see a case enter or leave a location, or notice that is is expired, or when it has been destroyed. This could either be by manual entry, or they could use 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 of the walkthrough, we will look at two more major features of the Platform that can enhance the usability of this tagged products use-case: roles and permissions, and data visualisation in the EVRYTHNG Dashboard.