Sending planning data as OTM5 file

To make sending planning data a lot easier, we make use of our universal planning inbox. The inbox is a single API which is able to receive planning data in different formats. One of the formats we are able to consume is JSON and specifically in the OTM5 standard. Underneath you will find more information on how to arrange your OTM5 file. Let's get started.

A look at the basic format

We are going to take you through the data structure, step by step. Let's take a look at the objects we will be using for the planning first:

Copy
Copied
{
  "id": "2a8af6c6-e4d5-5dfb-9532-e8b17c3ffd88",
  "name": "2021-32-2-888",
  "externalAttributes": {},
  "actors": [],
  "actions": []
}
id
uuid

Universally Unique Identifier to the trip.

name
string

Display name or identifier of this trip as typically seen in the URL that is unique within a shipper in the Simacan domain.

externalAttributes
object

Any attributes related to this trip

actors
array

All actors that are involved in this trip, and typically have access to at least its planning.

actions
array

All actions that are involved in this trip

Trip planning ruleset

We have implemented a few rules, to which an OTM5 planning should adhere, to make it a validated and valuable planning.

Trip level

  • A trip is required to have a name (display name), as shown in the basic structure
  • A trip is required to have actions: at least two stops are necessary.
  • A trip is required to have actors, a shipper and a carrier; this would look like the following:
Copy
Copied
   "actors": [
    {
      "associationType": "attributeRestriction",
      "restriction": {
        "externalAttributes": {
          "carrierId": "<my-carrier>"
        }
      },
      "entityType": "actor",
      "roles": ["carrier"]
    },
    {
      "associationType": "attributeRestriction",
      "restriction": {
        "externalAttributes": {
          "shipperId": "<my-shipper>"
        }
      },
      "entityType": "actor",
      "roles": ["shipper"]
    }
]
  • A trip optionally has a shift and/or tripId within the externalAttributes object
Copy
Copied
  "externalAttributes": {
    "tripId": "some-unique-id"
    "shift": "Evening shift"
  }

Stop level

A stop is a member of the actions array

  • A stop is required to have a location, startTime and an endTime.
  • A stop can have a load and unload action; these action have a consignment
  • A stop optionally has a name, remark and sequenceNr
  • A stop optionally has a stopId within an externalAttribute
  • A stop optionally has constraints; currently startDateTime and endDateTime are supported. Which in code looks like:
Copy
Copied
  {
    "id": "48c484f7-54ad-4209-b251-44abef26f6b0",
    "value": {
      "and": [
        {
          "startDateTime": "2021-03-12T10:00:00Z",
          "type": "startDateTimeConstraint"
        },
        {
          "endDateTime": "2021-03-12T11:00:00Z",
          "type": "endDateTimeConstraint"
        }
      ],
      "type": "andConstraint"
    }
  }

Consignment level

  • A load/unload action has an inline consignment, a startTime and an endTime
  • A consignment has a type (free text)
  • A consignment optionally has externalAttributes such as consignmentId, orderId, userId, trackTraceCode
  • A consignment optionally has goods. A good has a quantity and optionally a productType, barCode en remark
  • A complete consignment will more or less have the following structure in code (depending on what fields you fill of course):
Copy
Copied
{
  "id": "a556da93-46f3-4df5-8269-263bb337fde4",
  "externalAttributes": {
    "orderId": "<order id>",
    "userId": "<user id>",
    "trackTraceCode": "<t&t code>"
  },
  "type": "Frozen goods",
  "goods": [
    {
      "entity": {
        "id": "b74d1b1a-905a-42dd-91be-1fe77f443467",
        "remark": "Be careful not to melt it!",
        "barCode": "123456789",
        "quantity": 3,
        "productType": "Ice cream",
        "type": "items"
      },
      "associationType": "inline"
    }
  ]
}

Location level

  • A location can be sent to in two ways, either as 'restriction', or as a full address:

1. A location as restriction, only uses a locationId:

Copy
Copied
"location": {
  "associationType": "attributeRestriction",
  "restriction": {
    "externalAttributes": {
      "locationId": "someId"
    }
  },
  "entityType": "location"
}

2. A location as a full address requires a street, postalCode, city, country, name and optional a houseNumber and houseNumberExtension.

Copy
Copied
   "location": {
       "entity": {
       "id": "a1463169-8e75-4c0c-8d4f-fbeb408f53aa",
       "geoReference": {
           "name": "Simacan",
           "street": "Valutaboulevard",
           "houseNumber": "16",
           "postalCode": "1234AB",
           "city": "Amersfoort",
           "type": "addressGeoReference"
       }
       },
       "associationType": "inline"
   }
  • A location optionally has lat/lon-coordinates:
Copy
Copied
{
  "id": "440fffc6-3d2f-4cb2-9b98-93f0f1a87f3a",
  "geoReference": {
    "lat": 52.370216,
    "lon": 4.895168,
    "type": "latLonPointGeoReference"
  },
  "administrativeReference": {
    "name": "Simacan",
    "street": "Valutaboulevard",
    "houseNumber": "16",
    "postalCode": "1234AB",
    "city": "Amersfoort"
  }
}
  • A location optionally has contactDetails:
Copy
Copied
    "contactDetails": [
      {
        "value": "Simacan",
        "type": "name"
      },
      {
        "value": "0612345678",
        "type": "phone"
      },
      {
        "value": "sim@can.com",
        "type": "email"
      }
    ]

Full message example

Combining the ruleset above and taking into account required and optional fields, we can compose the following message. This is an example message, with three stops, all filled with dummy values. You could copy and paste and replace this with your own data.

Copy
Copied
{
  "id": "8aef6638-3828-45b6-9391-3e2882512345",
  "name": "OTM5 Trip Example",
  "externalAttributes": {
    "tripId": "25/04/21|OTM5-EXAMPLE-TRIP"
  },
  "actors": [{
    "associationType": "attributeRestriction",
    "restriction": {
      "externalAttributes": {
        "carrierId": "bbf1e201-c1a3-4205-a496-04a7e6f12345"
      }
    },
    "entityType": "actor",
    "roles": ["carrier"]
  },
  {
    "associationType": "attributeRestriction",
    "restriction": {
    "externalAttributes": {
      "shipperId": "08d6740f-0f4c-484f-8c0b-6c69f0d54321"
      }
  },
    "entityType": "actor",
    "roles": ["shipper"]
  }
],
  "actions": [{
    "entity": {
      "id": "66f041f1-cd50-4c0f-9c1a-dea046909876",
      "name": "S",
      "externalAttributes": {
        "stopId": "25/04/21|OTM5-EXAMPLE-TRIP-start"
      },
      "lifecycle": "planned",
      "location": {
        "associationType": "attributeRestriction",
        "restriction": {
          "externalAttributes": {
            "locationId": "HUB Amsterdam"
          }
        },
        "entityType": "location"
      },
      "startTime": "2021-04-25T17:36:00Z",
      "endTime": "2021-04-25T17:51:00Z",
      "actions": [{
        "entity": {
          "id": "e86bd334-f62c-45fe-8a8b-661cde187654",
          "consignment": {
            "entity": {
              "id": "6d0931ef-f9a1-4267-be7c-758aef154321",
              "type": "Products to load",
              "goods": [{
                "entity": {
                  "id": "3ddcac3b-c349-4e08-bab5-f39925145678",
                  "barCode": "x-0006",
                  "quantity": 12,
                  "productType": "Newspaper",
                  "type": "items"
                },
                "associationType": "inline"
              }, {
                "entity": {
                  "id": "86511ae4-8956-4df3-b3c5-0c0efc355555",
                  "barCode": "x-0012",
                  "quantity": 1,
                  "productType": "Book",
                  "type": "items"
                },
                "associationType": "inline"
              }, {
                "entity": {
                  "id": "7a6f6039-2f01-40a7-a557-6f2dbf022222",
                  "barCode": "x-0013",
                  "quantity": 1,
                  "productType": "Champagne",
                  "type": "items"
                },
                "associationType": "inline"
              }]
            },
            "associationType": "inline"
          },
          "startTime": "2021-04-25T17:36:00Z",
          "endTime": "2021-04-25T17:51:00Z",
          "actionType": "load"
        },
        "associationType": "inline"
      }],
      "constraint": {
        "entity": {
          "id": "be406065-048e-40c6-b212-0d0561188888",
          "value": {
            "and": [{
              "startDateTime": "2021-04-25T17:35:00Z",
              "type": "startDateTimeConstraint"
            }, {
              "endDateTime": "2021-04-25T18:35:00Z",
              "type": "endDateTimeConstraint"
            }],
            "type": "andConstraint"
          }
        },
        "associationType": "inline"
      },
      "actionType": "stop"
    },
    "associationType": "inline"
  }, {
    "entity": {
      "id": "1716d72d-d480-4035-9b19-5a92bfc88888",
      "name": "1",
      "externalAttributes": {
        "stopId": "25/04/21|OTM5-EXAMPLE-TRIP-SOME-STOP"
      },
      "lifecycle": "planned",
      "remark": "Doorbell is broken, please knock on door, place on doormat when not home",
      "location": {
        "entity": {
          "id": "dff6de51-a24c-4f8b-8a12-ea8205577777",
          "name": "Shirley",
          "geoReference": {
            "lat": 52.32686,
            "lon": 4.88846,
            "type": "latLonPointGeoReference"
          },
          "type": "customer",
          "administrativeReference": {
            "name": "Shirley",
            "street": "Nieuw Herlaer 2",
            "postalCode": "1083 BD",
            "city": "Amsterdam",
            "country": "NL"
          },
          "contactDetails": [{
            "value": "Shirley",
            "type": "firstName"
          }, {
            "value": "Shirley de Boer",
            "type": "name"
          }, {
            "value": "+31612345678",
            "type": "phone"
          }, {
            "value": "shirleydeboer@shotmail.com",
            "type": "email"
          }]
        },
        "associationType": "inline"
      },
      "startTime": "2021-04-25T18:01:00Z",
      "endTime": "2021-04-25T18:03:00Z",
      "actions": [{
        "entity": {
          "id": "3f992c74-867d-41f0-a9db-794ffa364444",
          "lifecycle": "planned",
          "consignment": {
            "entity": {
              "id": "a359a0f6-d695-45d6-9fa8-afcfb7233333",
              "externalAttributes": {
                "orderId": "6028716",
                "userId": "599271",
                "trackTraceCode": "602CLV05",
                "orderType": "Subscriptions"
              },
              "type": "OVERIG",
              "goods": [{
                "entity": {
                  "id": "86511ae4-8956-4df3-b3c5-0c0efc355555",
                  "barCode": "x-0012",
                  "quantity": 1,
                  "productType": "Book",
                  "type": "items"
                },
                "associationType": "inline"
              }, {
                "entity": {
                  "id": "3ddcac3b-c349-4e08-bab5-f39925145678",
                  "barCode": "x-0006",
                  "quantity": 1,
                  "productType": "Newspaper",
                  "type": "items"
                },
                "associationType": "inline"
              }]
            },
            "associationType": "inline"
          },
          "startTime": "2021-04-25T18:01:00Z",
          "endTime": "2021-04-25T18:03:00Z",
          "actionType": "unload"
        },
        "associationType": "inline"
      }],
      "constraint": {
        "entity": {
          "id": "6d6e0ab3-9ae0-4041-96bf-047861332222",
          "value": {
            "and": [{
              "startDateTime": "2021-04-25T18:00:00Z",
              "type": "startDateTimeConstraint"
            }, {
              "endDateTime": "2021-04-25T19:00:00Z",
              "type": "endDateTimeConstraint"
            }],
            "type": "andConstraint"
          }
        },
        "associationType": "inline"
      },
      "actionType": "stop"
    },
    "associationType": "inline"
  }, {
    "entity": {
      "id": "7b77e80d-527b-4700-8239-8d077fd99999",
      "name": "2",
      "externalAttributes": {
        "stopId": "25/04/21|OTM5-EXAMPLE-TRIP-SOME-STOP-2"
      },
      "lifecycle": "planned",
      "remark": "Leave at neighbours when not at home.",
      "location": {
        "entity": {
          "id": "ece51c99-0a6c-458d-ab44-91c77549bbbb",
          "name": "Martin",
          "geoReference": {
            "lat": 52.31945,
            "lon": 4.88408,
            "type": "latLonPointGeoReference"
          },
          "type": "customer",
          "administrativeReference": {
            "name": "Martin",
            "street": "Aanloop 3",
            "postalCode": "1183SZ",
            "city": "Amstelveen",
            "country": "NL"
          },
          "contactDetails": [{
            "value": "Martin",
            "type": "firstName"
          }, {
            "value": "Martin Golf",
            "type": "name"
          }, {
            "value": "+31687654321",
            "type": "phone"
          }, {
            "value": "martingolf@igoogle.nl",
            "type": "email"
          }]
        },
        "associationType": "inline"
      },
      "startTime": "2021-04-25T18:04:00Z",
      "endTime": "2021-04-25T18:06:00Z",
      "actions": [{
        "entity": {
          "id": "79597251-0aad-4401-9e34-e6367ef4ffff",
          "lifecycle": "planned",
          "consignment": {
            "entity": {
              "id": "e5f0528c-32b8-4a57-af15-57c297db4444",
              "externalAttributes": {
                "orderId": "6021923",
                "userId": "199283",
                "trackTraceCode": "60ABC04",
                "orderType": "Subscriptions"
              },
              "type": "OVERIG",
              "goods": [{
                "entity": {
                  "id": "7a6f6039-2f01-40a7-a557-6f2dbf022222",
                  "barCode": "x-0013",
                  "quantity": 1,
                  "productType": "Champagne",
                  "type": "items"
                },
                "associationType": "inline"
              }]
            },
            "associationType": "inline"
          },
          "startTime": "2021-04-25T18:04:00Z",
          "endTime": "2021-04-25T18:06:00Z",
          "actionType": "unload"
        },
        "associationType": "inline"
      }],
      "constraint": {
        "entity": {
          "id": "30918d21-763c-4287-bc85-b6f2436f2222",
          "value": {
            "and": [{
              "startDateTime": "2021-04-25T18:00:00Z",
              "type": "startDateTimeConstraint"
            }, {
              "endDateTime": "2021-04-25T19:00:00Z",
              "type": "endDateTimeConstraint"
            }],
            "type": "andConstraint"
          }
        },
        "associationType": "inline"
      },
      "actionType": "stop"
    },
    "associationType": "inline"
  }]
}

Updating your planning

Now that you have seen the appropriate format, you should also be aware that it is possible to update a planning that you sent to us before. It is important that sent messages always contains a full state, so a full planning. Any IDs we receive, will always be matched to any previous data, wherever possible. A trip id (UUID) used, that remained the same matched to a previous trip ID, will update the known trip. A new trip id (UUID) will secondly be checked on externalAttributes>tripId. If the UUID and the tripId don't match with a previous id, the new trip ID will be considered as a new trip. Stop IDs that remain the same, will be considered as the previous sent stop or changes to the previous sent stop. New stop IDs are considered as new stops. Removed stop IDs will be marked as cancelled stops.