# imports
from datetime import datetime
from dateutil.relativedelta import relativedelta
import pandas as pd
import vortexasdk as v
now = datetime.utcnow()
Let's start with a basic query to the SDK, for all Cargo Movements that are currently loading. We then look at one of these:
# basic query
cm_query = v.CargoMovements().search(
filter_activity="loading_state",
filter_time_min=now,
filter_time_max=now)
# remember - cm_query is a *list* of cargo movements
# taking a look at a single Cargo Movement
cm_query[0]
{'cargo_movement_id': '00a8b809a62f1b844d9586da3bad219e00a8b809a62f1b844d9586da3bad219e', 'quantity': 263385, 'status': 'loading_state', 'vessels': [{'id': '17fa9b4bcddf35051ba9aa2d1b04c6b4bba7eeefaa522a45dd07ec8e894416bb', 'mmsi': 248159000, 'imo': 9391957, 'name': 'ZAPPHIRE', 'dwt': 47329, 'cubic_capacity': 52466, 'vessel_class': 'handymax', 'corporate_entities': [{'id': 'bd551ce360a4175771b740a4efcce179d37992bf532c84358a115f78a2354198', 'label': 'So.Co.Mar', 'layer': 'commercial_owner', 'probability': 1, 'source': 'external'}], 'start_timestamp': '2021-02-08T04:19:05+0000', 'fixture_fulfilled': False, 'voyage_id': '9bd55e30686409e92e0aee35598af38614c0053da7efe4afb9b8d950c297227d', 'tags': [{'tag': 'vessel_coated_tag'}], 'status': 'vessel_status_laden_known', 'year': 2010, 'scrubber': [], 'flag': [{'tag': 'vessel_flag_tag', 'flag': 'MT', 'flag_country': '80dd61da7ce1edccaa43d2d60207c482e397bdd7f7efe7ad2a222d35dde2bc8c'}]}], 'product': [{'id': '5de0b00094e0fd7542c10f9f8a71b4008d55750f21dc905cda9b0f7f5f76bc08', 'layer': 'group', 'probability': 0.8644994, 'source': 'model', 'label': 'Dirty Petroleum Products'}, {'id': '1c107b4317bc2c85fb6c13cd7b28e8e0a02ec7fecc68afc2b68ca0545c835e1c', 'layer': 'group_product', 'probability': 0.8644994, 'source': 'model', 'label': 'Fuel Oil'}, {'id': '3fe831fb60183af885bc789b0224adfecfd3d911e9d2549981c1d7c092d30154', 'layer': 'category', 'probability': 0.8644994, 'source': 'model', 'label': 'High Sulphur Fuel Oil'}], 'events': [{'event_type': 'cargo_port_load_event', 'location': [{'id': 'ee1de4914cc26e8f1326b49793b089131870d478714c07e0c99c56cb307704c5', 'layer': 'country', 'label': 'Italy', 'source': 'model', 'probability': 1}, {'id': '29005fdc7273f96d4598adc2a8619b796875bd652b4c5a2b97b3283b92ca8dc8', 'layer': 'port', 'label': 'Augusta [IT]', 'source': 'model', 'probability': 1}, {'id': 'f39d455f5d38907394d6da3a91da4e391f9a34bd6a17e826d6042761067e88f4', 'layer': 'region', 'label': 'Europe', 'source': 'model', 'probability': 1}, {'id': '676c3cff1dffd971ae51cd350f231394eeb0ab94eff981432f6ee55c5219b913', 'layer': 'shipping_region', 'label': 'UKC-Med region', 'source': 'model', 'probability': 1}, {'id': 'a0a9eba4c7b8c1e853c207f32d67c4959b6197c39785e037abad42e90824c088', 'layer': 'shipping_region', 'label': 'West Mediterranean', 'source': 'model', 'probability': 1}, {'id': '6aa9a4fb76ca378e75eff85f458c90f7fd42778c26c957c417490c097161a3c5', 'layer': 'trading_region', 'label': 'Mediterranean (incl. North Africa)', 'source': 'model', 'probability': 1}, {'id': 'ee1de4914cc26e8f1326b49793b089131870d478714c07e0c99c56cb307704c5', 'layer': 'trading_subregion', 'label': 'Italy', 'source': 'model', 'probability': 1}, {'id': '5e5deb2ca3810bf061e7cc540bb6a352651ab1f8b2feced92d285320a43b1468', 'layer': 'terminal', 'label': 'Augusta Refinery ESSO', 'source': 'model', 'probability': 1}], 'probability': 1, 'pos': [15.193458283160899, 37.20789046681706], 'start_timestamp': '2021-02-08T04:19:05+0000', 'end_timestamp': '2021-02-09T16:15:06+0000'}], 'parent_ids': []}
There's a lot of information here. Note that this is a 'dictionary' structure, so we can put out the top level keys, and their type:
cm_query[0].keys()
dict_keys(['cargo_movement_id', 'quantity', 'status', 'vessels', 'product', 'events', 'parent_ids'])
print([type(cm_query[0][cmk]) for cmk in cm_query[0]])
[<class 'str'>, <class 'int'>, <class 'str'>, <class 'list'>, <class 'list'>, <class 'list'>, <class 'list'>]
Three of these keys correspond to individual vales which we can print out:
print('cargo_movement_id:', cm_query[0]['cargo_movement_id'])
print('quantity:', cm_query[0]['quantity'])
print('status:', cm_query[0]['status'])
cargo_movement_id: 00a8b809a62f1b844d9586da3bad219e00a8b809a62f1b844d9586da3bad219e quantity: 263385 status: loading_state
The remaning keys are lists, which we now now describe in turn.
vessels
contains a VesselEntity
for each vessel involved in the Cargo Movement. In general, there can be multiple vessels, but we shall touch on this later. The Cargo Movement we're looking only contains one VesselEntity
:
vessels = cm_query[1]['vessels']
len(vessels)
1
vessels[0]
{'id': 'c101bba621fa1a4f3a1d1f57c3b800f6976def9448a31e42677ddbb0ea705604', 'mmsi': 538003436, 'imo': 9515436, 'name': 'M.STAR', 'dwt': 314016, 'cubic_capacity': 344553, 'vessel_class': 'vlcc_plus', 'corporate_entities': [{'id': 'd4a185c32e0e38bb5fe45b23f6d880f35af53a6e4337e74648515832875f24f4', 'label': 'SK', 'layer': 'charterer', 'probability': 1, 'source': 'external'}, {'id': '1ed67e6d2c516dc0867162777e1b2e042ed8f4eed6602865402930d853cf1f66', 'label': 'SK GROUP', 'layer': 'commercial_owner', 'probability': 1, 'source': 'external'}], 'start_timestamp': '2021-02-07T23:02:19+0000', 'fixture_id': '29e6c45ded5d41ee57a6dc46881e2998477e99d329edd8b9d0f867ef343ea431', 'fixture_fulfilled': True, 'voyage_id': '2192d7a78c8ccf617212a1856e4bd43b97992ddd9053ff0dcd35872b9fa68219', 'tags': [], 'status': 'vessel_status_laden_known', 'year': 2008, 'scrubber': [{'tag': 'vessel_scrubber_tag', 'scrubber': '478fca39000c49d6', 'planned': False}], 'flag': [{'tag': 'vessel_flag_tag', 'flag': 'MH', 'flag_country': 'e084d7507dab6604894c203b3834cd7ce8f16385daeae56e202cd9e930f788d2'}]}
products
contains Product Entries, which each describe one layer of the product tree (Group, Group Product, Category, and Grade). Not all products specify a Grade. We can view the Product Entries in a DataFrame:
products = cm_query[0]['product']
pd.DataFrame(products)
id | layer | probability | source | label | |
---|---|---|---|---|---|
0 | 5de0b00094e0fd7542c10f9f8a71b4008d55750f21dc90... | group | 0.864499 | model | Dirty Petroleum Products |
1 | 1c107b4317bc2c85fb6c13cd7b28e8e0a02ec7fecc68af... | group_product | 0.864499 | model | Fuel Oil |
2 | 3fe831fb60183af885bc789b0224adfecfd3d911e9d254... | category | 0.864499 | model | High Sulphur Fuel Oil |
events
contains Cargo Events, which each describe a specific event happening to the cargo at a specific time. These can be:
In our example, the two events are:
events = cm_query[1]['events']
[e['event_type'] for e in events]
['cargo_port_load_event']
We can see some high level information about these events by putting them into a DataFrame:
pd.DataFrame(events)
event_type | location | probability | pos | start_timestamp | end_timestamp | |
---|---|---|---|---|---|---|
0 | cargo_port_load_event | [{'id': '6253047d839a51684f2ab6e3a4fa9956f582c... | 1 | [48.32317414902807, 29.142766459317272] | 2021-02-07T23:02:19+0000 | 2021-02-09T14:00:10+0000 |
The 'location' entries are themselves dictionaries, which we can expand into a DataFrame to see the different layers:
pd.DataFrame(events[0]['location'])
id | layer | label | source | probability | |
---|---|---|---|---|---|
0 | 6253047d839a51684f2ab6e3a4fa9956f582c73dfbbb27... | country | Kuwait | model | 1 |
1 | f8c1ff7397acf5d2e353a369fa399b310591680c7a862c... | port | Mina Al Ahmadi [KW] | model | 1 |
2 | 80aa9e4f3014c3d96559c8e642157edbb2b684ea0144ed... | region | Middle East | model | 1 |
3 | 0899599f74faadb7ba7eb65205ee5c20cb434367a6e720... | shipping_region | MEG/AG | model | 1 |
4 | 5057dafe08229da478858da705209d61d88db8dcaadf83... | trading_block | OPEC | model | 1 |
5 | a7536c48714140c7ba8e8895cdcccc12ebb2c4813720d6... | trading_block | OPEC + Russia | model | 1 |
6 | 0899599f74faadb7ba7eb65205ee5c20cb434367a6e720... | trading_region | MEG/AG | model | 1 |
7 | 6253047d839a51684f2ab6e3a4fa9956f582c73dfbbb27... | trading_subregion | Kuwait | model | 1 |
8 | 1d4d3d8565ea23172f9ea72d3daebc642e835171bccf26... | terminal | KNPC SBMs | model | 1 |
A point to keep in mind
In this lesson we have dived into the structure of a Cargo Movement. While important for understanding, in practice it is often simpler to just use cm_query.to_df('all') to convert all these records to a familiar tabular form, without paying attention to the structure. This is true of other endpoints as well.
The Vortexa SDK also offers a VesselMovements
endpoint. Vessel Movements can be searched for in a similar way to Cargo Movements, but they have some differences. Do a query for Vessel Movements, and inspect the structure of a Vessel Movement to identify the differences.