vortexa-claude-skills 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/VERSION +1 -0
- package/bin/.gitkeep +0 -0
- package/bin/setup.js +302 -0
- package/commands/vortexa/_check-setup.md +9 -0
- package/commands/vortexa/_skill-template.md +100 -0
- package/commands/vortexa/breakdown.md +294 -0
- package/commands/vortexa/cargo-flows.md +247 -0
- package/commands/vortexa/compare.md +315 -0
- package/commands/vortexa/custom.md +214 -0
- package/commands/vortexa/explain.md +124 -0
- package/commands/vortexa/init.md +133 -0
- package/commands/vortexa/oow.md +189 -0
- package/commands/vortexa/seasonal.md +185 -0
- package/commands/vortexa/voyages.md +285 -0
- package/context/.gitkeep +0 -0
- package/context/cargo-movements.md +738 -0
- package/context/date-units.md +188 -0
- package/context/endpoint-template.md +176 -0
- package/context/entity-resolution.md +217 -0
- package/context/guardrails.md +161 -0
- package/context/reference-endpoints.md +651 -0
- package/context/voyages.md +636 -0
- package/lib/__init__.py +4 -0
- package/lib/aliases.json +52 -0
- package/lib/api.py +20 -0
- package/lib/entities.py +254 -0
- package/lib/inventory.py +140 -0
- package/lib/movements.py +242 -0
- package/lib/requirements.txt +6 -0
- package/lib/seasonal.py +200 -0
- package/lib/timeseries.py +271 -0
- package/lib/utils.py +120 -0
- package/lib/vessels.py +192 -0
- package/lib/visualization.py +164 -0
- package/lib/voyages.py +236 -0
- package/package.json +28 -0
- package/templates/.env.template +3 -0
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
# Reference Endpoints
|
|
2
|
+
|
|
3
|
+
> Complete reference for Products, Geographies, Vessels, and Corporations entity endpoints.
|
|
4
|
+
> For the entity resolution workflow (how to resolve names to IDs), see entity-resolution.md (pre-loaded).
|
|
5
|
+
> This doc covers what the API supports -- full parameter tables, hierarchies, and SDK/REST examples.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Entity Hierarchy Overview
|
|
10
|
+
|
|
11
|
+
All Vortexa data endpoints require 64-char hex IDs. Use these reference endpoints to resolve human-readable names.
|
|
12
|
+
|
|
13
|
+
| Entity Type | SDK Class | Hierarchy (widest to most specific) | Key Filter |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| Product | `Products` | group > group_product > category > grade | `filter_layer` |
|
|
16
|
+
| Geography | `Geographies` | wider_shipping_region > shipping_region_v2 > region > country > port > terminal | `filter_layer` |
|
|
17
|
+
| Corporation | `Corporations` | effective_controller > commercial_owner > time_charterer > charterer | (none in SDK) |
|
|
18
|
+
| Vessel | `Vessels` | oil / lpg / lng > grouped class > detailed class | `vessel_classes` |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Products
|
|
23
|
+
|
|
24
|
+
### Hierarchy
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
group (widest)
|
|
28
|
+
+-- group_product
|
|
29
|
+
+-- category
|
|
30
|
+
+-- grade (most specific)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
| Layer | Example | When to Use |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `group` | Crude & Condensates, Clean Petroleum Products, Dirty Petroleum Products, LNG, LPG/NGL | High-level commodity analysis |
|
|
36
|
+
| `group_product` | Crude, Condensates, Gasoline/Naphtha, Fuel Oil, Gasoil/Diesel | Product family breakdown |
|
|
37
|
+
| `category` | Medium-Sour, Light-Sweet, Diesel/Gasoil | Mid-level product classification |
|
|
38
|
+
| `grade` | Arab Light, Bonny Light, Grane | Specific crude grade or refined product |
|
|
39
|
+
|
|
40
|
+
### Product Groups (Top Level)
|
|
41
|
+
|
|
42
|
+
These are the root-level product groups in Vortexa:
|
|
43
|
+
- Crude & Condensates
|
|
44
|
+
- Clean Petroleum Products
|
|
45
|
+
- Dirty Petroleum Products
|
|
46
|
+
- LNG
|
|
47
|
+
- LPG/NGL
|
|
48
|
+
- Chemicals
|
|
49
|
+
- Vegetable Oils
|
|
50
|
+
|
|
51
|
+
### SDK Methods
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from vortexasdk import Products
|
|
55
|
+
|
|
56
|
+
# Search by name with layer filter
|
|
57
|
+
df = Products().search(term="Arab Light", filter_layer="grade").to_df()
|
|
58
|
+
|
|
59
|
+
# Search multiple terms
|
|
60
|
+
df = Products().search(term=["diesel", "fuel oil", "grane"]).to_df("all")
|
|
61
|
+
|
|
62
|
+
# Get children of a parent product
|
|
63
|
+
df = Products().search(product_parent="<parent_id>").to_df()
|
|
64
|
+
|
|
65
|
+
# Lookup by ID
|
|
66
|
+
record = Products().reference(id="abc123...")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**search() parameters:**
|
|
70
|
+
|
|
71
|
+
| Parameter | Type | Description |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `term` | str or List[str] | Product name(s) to search |
|
|
74
|
+
| `ids` | str or List[str] | Specific product IDs |
|
|
75
|
+
| `product_parent` | str or List[str] | Parent product ID(s) -- returns direct children |
|
|
76
|
+
| `exact_term_match` | bool | Default `False` |
|
|
77
|
+
| `filter_layer` | str | One of: `group`, `group_product`, `category`, `grade` |
|
|
78
|
+
|
|
79
|
+
**Default DataFrame columns:** `id`, `name`, `layer.0`, `parent.0.name`
|
|
80
|
+
|
|
81
|
+
**Full columns** (use `columns="all"`): includes `parent.0.id`, `parent.0.layer.0`, `meta.api_min`, `meta.api_max`, `meta.sulphur_min`, `meta.sulphur_max`, `ref_type`, `leaf`
|
|
82
|
+
|
|
83
|
+
### REST API Alternative
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import requests
|
|
87
|
+
|
|
88
|
+
base = "https://api.vortexa.com"
|
|
89
|
+
headers = {"x-api-key": VORTEXA_API_KEY}
|
|
90
|
+
|
|
91
|
+
payload = {"term": "Arab Light", "filter_layer": ["grade"], "size": 10}
|
|
92
|
+
resp = requests.post(f"{base}/v5/reference/products", headers=headers, json=payload)
|
|
93
|
+
products = resp.json()["data"]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**REST-specific parameters (not in SDK):**
|
|
97
|
+
|
|
98
|
+
| Parameter | Type | Description |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `product_parent` | str | Parent ID or `"TOP_LEVEL"` to get root-level products |
|
|
101
|
+
| `product_ancestor` | str[] | Ancestor IDs (returns all descendants) |
|
|
102
|
+
| `allowTopLevelProducts` | bool | Default `true`. Include top-level products |
|
|
103
|
+
|
|
104
|
+
### Product Entries in API Responses
|
|
105
|
+
|
|
106
|
+
Cargo movement/voyage responses embed one product entry per hierarchy layer:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
[
|
|
110
|
+
{"id": "...", "layer": "group", "label": "Crude & Condensates", "probability": 0.94, "source": "model"},
|
|
111
|
+
{"id": "...", "layer": "group_product", "label": "Crude", "probability": 0.94, "source": "model"},
|
|
112
|
+
{"id": "...", "layer": "category", "label": "Medium-Sour", "probability": 0.94, "source": "model"},
|
|
113
|
+
{"id": "...", "layer": "grade", "label": "Arab Light", "probability": 0.94, "source": "model"}
|
|
114
|
+
]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
To extract a specific level: filter where `layer == "grade"` (or whichever level you need).
|
|
118
|
+
|
|
119
|
+
### CRITICAL RULES -- Products
|
|
120
|
+
|
|
121
|
+
- **ALWAYS** specify `filter_layer` to avoid matching the wrong hierarchy level
|
|
122
|
+
- **ALWAYS** verify the `layer` and `name` columns in results -- fuzzy matching may return grades when you want the group
|
|
123
|
+
- **NEVER** assume "crude" matches only one product -- without `filter_layer`, it returns group, group_product, and grade matches
|
|
124
|
+
- **ALWAYS** set `size` >= 10 for resolution searches (default `size=1` may return the wrong match)
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Geographies
|
|
129
|
+
|
|
130
|
+
### Hierarchy
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
wider_shipping_region (widest)
|
|
134
|
+
+-- shipping_region_v2
|
|
135
|
+
+-- region
|
|
136
|
+
+-- country
|
|
137
|
+
+-- port
|
|
138
|
+
+-- terminal (most specific)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Additional geography layers (not in main hierarchy):**
|
|
142
|
+
- `sts_zone` -- Ship-to-ship transfer zones
|
|
143
|
+
- `waypoint` -- Chokepoints and transit points (e.g. Suez, Panama)
|
|
144
|
+
- `trading_block`, `trading_region`, `trading_subregion` -- Trade-oriented groupings
|
|
145
|
+
- `country_zone`, `state_or_province` -- Sub-country divisions
|
|
146
|
+
- `basin` -- Ocean basins
|
|
147
|
+
- `storage`, `storage_terminal` -- Onshore storage locations
|
|
148
|
+
|
|
149
|
+
### SDK Methods
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from vortexasdk import Geographies
|
|
153
|
+
|
|
154
|
+
# Search by name with layer filter
|
|
155
|
+
df = Geographies().search(term="Rotterdam", filter_layer="port").to_df()
|
|
156
|
+
|
|
157
|
+
# Search multiple terms
|
|
158
|
+
df = Geographies().search(term=["Liverpool", "Southampton"]).to_df()
|
|
159
|
+
|
|
160
|
+
# Exact match only
|
|
161
|
+
df = Geographies().search(term="China", exact_term_match=True, filter_layer="country").to_df()
|
|
162
|
+
|
|
163
|
+
# Lookup by ID
|
|
164
|
+
record = Geographies().reference(id="abc123...")
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**search() parameters:**
|
|
168
|
+
|
|
169
|
+
| Parameter | Type | Description |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| `term` | str or List[str] | Geography name(s) to search |
|
|
172
|
+
| `exact_term_match` | bool | Default `False`. If `True`, only exact name matches |
|
|
173
|
+
| `filter_layer` | str | One of: `terminal`, `port`, `country`, `country_zone`, `shipping_region`, `shipping_region_v2`, `wider_shipping_region`, `region`, `alternative_region`, `trading_block`, `trading_region`, `trading_subregion`, `state_or_province`, `sts_zone`, `waypoint`, `storage`, `storage_terminal`, `basin`, `root` |
|
|
174
|
+
|
|
175
|
+
**Default DataFrame columns:** `id`, `name`, `layer`
|
|
176
|
+
|
|
177
|
+
**REST-specific parameters (not in SDK):**
|
|
178
|
+
|
|
179
|
+
| Parameter | Type | Description |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| `geography_parent` | str[] | Parent ID(s) or `"TOP_LEVEL"` to get root-level geographies |
|
|
182
|
+
| `only_root` | bool | Omit parent/hierarchy data from results |
|
|
183
|
+
|
|
184
|
+
### Common Geography Lookups
|
|
185
|
+
|
|
186
|
+
| Common Name / Abbreviation | filter_layer | Vortexa Name | Notes |
|
|
187
|
+
|---|---|---|---|
|
|
188
|
+
| "China", "US", "Saudi Arabia" | `country` | China, United States, Saudi Arabia | Country names |
|
|
189
|
+
| "Rotterdam", "Fujairah", "Ras Tanura" | `port` | Rotterdam [NL], Fujairah, Ras Tanura | Port names |
|
|
190
|
+
| "Middle East", "Asia", "Europe" | `region` | Middle East/North Africa, Asia, Europe | Broad regions |
|
|
191
|
+
| "AG", "Arabian Gulf", "Persian Gulf" | `shipping_region_v2` | Arabian/Persian Gulf | Shipping regions |
|
|
192
|
+
| "USG", "US Gulf Coast" | `shipping_region_v2` | US Gulf Coast | Shipping regions |
|
|
193
|
+
| "WAF", "West Africa" | `shipping_region_v2` | West Africa | Shipping regions |
|
|
194
|
+
| "ARA" | `port` (multiple) | Amsterdam, Rotterdam, Antwerp | Resolve each port separately |
|
|
195
|
+
| "PADD I", "East Coast" | `shipping_region_v2` | US Atlantic Coast | US regional |
|
|
196
|
+
| "Suez Canal", "Panama Canal" | `waypoint` | Suez Canal, Panama Canal | Chokepoints |
|
|
197
|
+
|
|
198
|
+
### REST API Alternative
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
import requests
|
|
202
|
+
|
|
203
|
+
base = "https://api.vortexa.com"
|
|
204
|
+
headers = {"x-api-key": VORTEXA_API_KEY}
|
|
205
|
+
|
|
206
|
+
payload = {"term": "Rotterdam", "filter_layer": ["port"], "size": 10}
|
|
207
|
+
resp = requests.post(f"{base}/v5/reference/geographies", headers=headers, json=payload)
|
|
208
|
+
geos = resp.json()["data"]
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Geography Entries in API Responses
|
|
212
|
+
|
|
213
|
+
Location arrays in cargo events contain one entry per hierarchy layer:
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
[
|
|
217
|
+
{"id": "...", "layer": "terminal", "label": "Poleng Marine Terminal", "probability": 1, "source": "model"},
|
|
218
|
+
{"id": "...", "layer": "port", "label": "Poleng Field [ID]", "probability": 1, "source": "model"},
|
|
219
|
+
{"id": "...", "layer": "country", "label": "Indonesia", "probability": 1, "source": "model"},
|
|
220
|
+
{"id": "...", "layer": "region", "label": "Asia", "probability": 1, "source": "model"}
|
|
221
|
+
]
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### CRITICAL RULES -- Geographies
|
|
225
|
+
|
|
226
|
+
- **ALWAYS** specify `filter_layer` -- "China" without a layer returns ports, regions, AND the country
|
|
227
|
+
- **NEVER** use a port ID when a country was intended (volumes will be drastically wrong)
|
|
228
|
+
- **NEVER** search geographies without `filter_layer` in production queries
|
|
229
|
+
- **ALWAYS** use `exact_term_match=True` for well-known names to avoid fuzzy noise
|
|
230
|
+
- **ALWAYS** set `size` >= 10 for resolution searches
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Vessels
|
|
235
|
+
|
|
236
|
+
### SDK Methods
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
from vortexasdk import Vessels
|
|
240
|
+
|
|
241
|
+
# Search by name
|
|
242
|
+
df = Vessels().search(term="ocean", vessel_classes="oil_vlcc").to_df()
|
|
243
|
+
|
|
244
|
+
# Search by IMO
|
|
245
|
+
df = Vessels().search(ids=[9779836]).to_df(columns="all")
|
|
246
|
+
|
|
247
|
+
# Filter by class and scrubber
|
|
248
|
+
df = Vessels().search(
|
|
249
|
+
vessel_classes=["oil_aframax", "oil_suezmax"],
|
|
250
|
+
vessel_scrubbers="inc"
|
|
251
|
+
).to_df()
|
|
252
|
+
|
|
253
|
+
# Lookup by ID
|
|
254
|
+
record = Vessels().reference(id="abc123...")
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**search() parameters:**
|
|
258
|
+
|
|
259
|
+
| Parameter | Type | Description |
|
|
260
|
+
|---|---|---|
|
|
261
|
+
| `term` | str or List[str] | Vessel name(s) to search. Also matches `related_names`. |
|
|
262
|
+
| `ids` | str or List[str] | Vessel IDs, IMO numbers, or MMSI numbers |
|
|
263
|
+
| `vessel_classes` | str or List[str] | Vessel class filter (see class reference below) |
|
|
264
|
+
| `vessel_scrubbers` | str | `"disabled"` (default), `"inc"`, or `"exc"` |
|
|
265
|
+
| `exact_term_match` | bool | Default `False` |
|
|
266
|
+
|
|
267
|
+
**Default DataFrame columns:** `id`, `name`, `imo`, `vessel_class`
|
|
268
|
+
|
|
269
|
+
**Additional columns** (use `columns="all"`): `mmsi`, `dwt`, `cubic_capacity`, `related_names`, `year`, `flag`, `scrubber`, `ice_class`, `propulsion`, `tags`
|
|
270
|
+
|
|
271
|
+
**REST-specific parameters (not in SDK):**
|
|
272
|
+
|
|
273
|
+
| Parameter | Type | Description |
|
|
274
|
+
|---|---|---|
|
|
275
|
+
| `vessel_tags` | object[] | Filter by vessel tags (e.g. dark activity, FSO) |
|
|
276
|
+
| `vessel_tags_excluded` | object[] | Exclude vessels with these tags |
|
|
277
|
+
| `dwt_min` / `dwt_max` | number | DWT range filter (0-550,000) |
|
|
278
|
+
| `ship_to_ship_only` | bool | Only STS-capable vessels |
|
|
279
|
+
|
|
280
|
+
### Vessel Class Reference
|
|
281
|
+
|
|
282
|
+
**User Term to API Vessel Class mapping:**
|
|
283
|
+
|
|
284
|
+
| User Says | API `vessel_classes` Value | Typical Size |
|
|
285
|
+
|---|---|---|
|
|
286
|
+
| "VLCC" | `oil_vlcc` | 200,000+ DWT |
|
|
287
|
+
| "Suezmax" | `oil_suezmax` | 120,000-200,000 DWT |
|
|
288
|
+
| "Aframax" | `oil_aframax` | 80,000-120,000 DWT |
|
|
289
|
+
| "Panamax" (oil) | `oil_panamax` | 55,000-80,000 DWT |
|
|
290
|
+
| "MR2", "Handymax" | `oil_handymax` or `oil_mr2` | 40,000-55,000 DWT |
|
|
291
|
+
| "MR1", "Handysize" | `oil_handysize` or `oil_mr1` | 25,000-40,000 DWT |
|
|
292
|
+
| "LR1" | `oil_lr1` | 55,000-80,000 DWT |
|
|
293
|
+
| "LR2" | `oil_lr2` | 80,000-120,000 DWT |
|
|
294
|
+
| "LR3" | `oil_lr3` | 120,000-200,000 DWT |
|
|
295
|
+
| "Coastal tanker" | `oil_coastal` | <25,000 DWT |
|
|
296
|
+
| "VLGC" | `lpg_vlgc` | 60,000+ CBM |
|
|
297
|
+
| "LGC" | `lpg_lgc` | 35,000-60,000 CBM |
|
|
298
|
+
| "MGC" | `lpg_mgc` | 15,000-35,000 CBM |
|
|
299
|
+
| "Q-Flex", "Q-Max" | `lng_q_flex`, `lng_q_max` | Qatar Q-class mega |
|
|
300
|
+
| "LNG carrier" | `lng_conventional_lng` | Standard LNG |
|
|
301
|
+
| "All oil tankers" | `oil` | All oil subclasses |
|
|
302
|
+
| "All LPG carriers" | `lpg` | All LPG subclasses |
|
|
303
|
+
| "All LNG carriers" | `lng` | All LNG subclasses |
|
|
304
|
+
|
|
305
|
+
**Grouped classes** (each group aggregates multiple detailed classes):
|
|
306
|
+
|
|
307
|
+
| Grouped Class | Detailed Classes |
|
|
308
|
+
|---|---|
|
|
309
|
+
| `oil_suezmax_lr3` | `oil_suezmax`, `oil_lr3` |
|
|
310
|
+
| `oil_aframax_lr2` | `oil_aframax`, `oil_lr2` |
|
|
311
|
+
| `oil_panamax_lr1` | `oil_panamax`, `oil_lr1` |
|
|
312
|
+
| `oil_handymax_mr2` | `oil_handymax`, `oil_mr2` |
|
|
313
|
+
| `oil_handysize_mr1` | `oil_handysize`, `oil_mr1` |
|
|
314
|
+
| `lpg_vlgc_vlec` | `lpg_vlgc`, `lpg_vlec` |
|
|
315
|
+
| `lpg_sgc` | `lpg_handysize`, `lpg_coasters` |
|
|
316
|
+
| `lng_conventional_lng` | `lng_two_stroke`, `lng_tfde_dfde`, `lng_steam`, `lng_ssd` |
|
|
317
|
+
|
|
318
|
+
Top-level aggregates: `oil`, `lpg`, `lng` -- match all subclasses within that group.
|
|
319
|
+
|
|
320
|
+
### Vessel Entities in API Responses
|
|
321
|
+
|
|
322
|
+
Vessel objects in cargo movement responses contain:
|
|
323
|
+
|
|
324
|
+
| Field | Type | Description |
|
|
325
|
+
|---|---|---|
|
|
326
|
+
| `id` | string | Vortexa vessel ID (64-char hex) |
|
|
327
|
+
| `imo` | number | IMO number |
|
|
328
|
+
| `mmsi` | number | MMSI number |
|
|
329
|
+
| `name` | string | Vessel name |
|
|
330
|
+
| `dwt` | number | Deadweight tonnage |
|
|
331
|
+
| `cubic_capacity` | number | Cubic capacity (m3) |
|
|
332
|
+
| `vessel_class` | string | Vessel class code |
|
|
333
|
+
| `corporate_entities` | array | Array of corporate entity objects |
|
|
334
|
+
| `start_timestamp` | string | When cargo was loaded onto this vessel |
|
|
335
|
+
| `end_timestamp` | string or null | When cargo left (null if still onboard) |
|
|
336
|
+
| `voyage_id` | string or null | Linked voyage ID |
|
|
337
|
+
| `tags` | array | Time-bound vessel tags (e.g. FSO, dark activity) |
|
|
338
|
+
| `status` | string | e.g. `vessel_status_laden_known`, `vessel_status_ballast` |
|
|
339
|
+
| `year` | number or null | Build year |
|
|
340
|
+
| `flag` | array | Flag state info |
|
|
341
|
+
| `scrubber` | array | Scrubber info |
|
|
342
|
+
|
|
343
|
+
### REST API Alternative
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
import requests
|
|
347
|
+
|
|
348
|
+
base = "https://api.vortexa.com"
|
|
349
|
+
headers = {"x-api-key": VORTEXA_API_KEY}
|
|
350
|
+
|
|
351
|
+
# Search by IMO
|
|
352
|
+
payload = {"term": [9779836], "size": 10}
|
|
353
|
+
resp = requests.post(f"{base}/v5/reference/vessels", headers=headers, json=payload)
|
|
354
|
+
vessels = resp.json()["data"]
|
|
355
|
+
|
|
356
|
+
# Get vessel by ID
|
|
357
|
+
vessel_id = "1f6d6ab80d90846cedddf1a4b470faf566fd72595e853bc769185fac6ae7af70"
|
|
358
|
+
resp = requests.get(f"{base}/v5/reference/vessels/{vessel_id}", headers=headers)
|
|
359
|
+
vessel = resp.json()["data"][0]
|
|
360
|
+
|
|
361
|
+
# Get IDs only (lighter response)
|
|
362
|
+
payload = {"vessel_classes": ["oil_vlcc"], "size": 500}
|
|
363
|
+
resp = requests.post(f"{base}/v5/reference/vessels-ids", headers=headers, json=payload)
|
|
364
|
+
# Supports return_type: "short" for 16-char truncated IDs
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### CRITICAL RULES -- Vessels
|
|
368
|
+
|
|
369
|
+
- **NEVER** pass IMO numbers as strings -- the API requires integers in the `ids` or `term` array
|
|
370
|
+
- **ALWAYS** use `vessel_classes` filter in Vessels endpoint for name/IMO/class lookup only
|
|
371
|
+
- Vessel class filtering for data queries (CargoMovements/Voyages) happens via `filter_vessel_classes` in the query, not in the Vessels reference endpoint
|
|
372
|
+
- For vessel class filtering in CM/Voyages: use `filter_vessel_classes=["oil_vlcc"]` directly -- no ID resolution needed
|
|
373
|
+
- **ALWAYS** set `size` >= 10 for resolution searches
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## Corporations
|
|
378
|
+
|
|
379
|
+
### SDK Methods
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
from vortexasdk import Corporations
|
|
383
|
+
|
|
384
|
+
# Search by name
|
|
385
|
+
df = Corporations().search(term="BAHRI").to_df()
|
|
386
|
+
|
|
387
|
+
# Exact match
|
|
388
|
+
df = Corporations().search(term="CHEVRON", exact_term_match=True).to_df()
|
|
389
|
+
|
|
390
|
+
# Lookup by ID
|
|
391
|
+
record = Corporations().reference(id="abc123...")
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
**search() parameters:**
|
|
395
|
+
|
|
396
|
+
| Parameter | Type | Description |
|
|
397
|
+
|---|---|---|
|
|
398
|
+
| `term` | str or List[str] | Corporation name(s) to search |
|
|
399
|
+
| `exact_term_match` | bool | Default `False` |
|
|
400
|
+
|
|
401
|
+
**Default DataFrame columns:** `id`, `name`, `corporate_entity_type`
|
|
402
|
+
|
|
403
|
+
### Corporate Hierarchy
|
|
404
|
+
|
|
405
|
+
```
|
|
406
|
+
effective_controller (ultimate owner)
|
|
407
|
+
+-- commercial_owner
|
|
408
|
+
+-- time_charterer
|
|
409
|
+
+-- charterer (voyage-level)
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Additional corporate entity types (API only):
|
|
413
|
+
- `cargo_trader` -- Commodity trading house
|
|
414
|
+
- `shipper` -- Party shipping the cargo
|
|
415
|
+
- `consignee` -- Receiving party
|
|
416
|
+
- `buyer_seller` -- Transaction counterparty
|
|
417
|
+
|
|
418
|
+
### REST API Alternative (Required for Entity Type Filtering)
|
|
419
|
+
|
|
420
|
+
The SDK `Corporations` class wraps the API's `/reference/charterers` endpoint but does **NOT** expose `corporateEntityType` filtering. Use REST directly when you need to filter by role:
|
|
421
|
+
|
|
422
|
+
```python
|
|
423
|
+
import requests
|
|
424
|
+
|
|
425
|
+
base = "https://api.vortexa.com"
|
|
426
|
+
headers = {"x-api-key": VORTEXA_API_KEY}
|
|
427
|
+
|
|
428
|
+
# Search corporations by entity type (REST only)
|
|
429
|
+
payload = {
|
|
430
|
+
"term": "CHEVRON",
|
|
431
|
+
"corporateEntityType": ["effective_controller"],
|
|
432
|
+
"size": 10
|
|
433
|
+
}
|
|
434
|
+
resp = requests.post(f"{base}/v5/reference/charterers", headers=headers, json=payload)
|
|
435
|
+
corps = resp.json()["data"]
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
**REST `corporateEntityType` values:** `effective_controller`, `charterer`, `time_charterer`, `commercial_owner`, `cargo_trader`, `shipper`, `consignee`, `buyer_seller`, `all`
|
|
439
|
+
|
|
440
|
+
### Corporate Entities in API Responses
|
|
441
|
+
|
|
442
|
+
The `corporate_entities` array in vessel objects contains one entry per role:
|
|
443
|
+
|
|
444
|
+
```json
|
|
445
|
+
[
|
|
446
|
+
{"id": "...", "layer": "effective_controller", "label": "BAHRI", "probability": 1, "source": "external"},
|
|
447
|
+
{"id": "...", "layer": "charterer", "label": "CHEVRON", "probability": 1, "source": "external"}
|
|
448
|
+
]
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Not all roles are always present -- a vessel may have an effective_controller but no charterer.
|
|
452
|
+
|
|
453
|
+
### CRITICAL RULES -- Corporations
|
|
454
|
+
|
|
455
|
+
- **SDK limitation:** `Corporations` class does NOT expose `corporateEntityType` filter -- use REST API for role-based filtering
|
|
456
|
+
- **ALWAYS** use `exact_term_match=True` when searching well-known company names to avoid fuzzy noise (e.g. "COS" returns COSCO, COSMO OIL, etc.)
|
|
457
|
+
- **NEVER** assume a single corporate entity has only one role -- the same company can appear as both effective_controller and charterer
|
|
458
|
+
- **ALWAYS** set `size` >= 10 for resolution searches
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
## Additional Reference Endpoints
|
|
463
|
+
|
|
464
|
+
These endpoints are less frequently used but available for specialized queries.
|
|
465
|
+
|
|
466
|
+
### Attributes
|
|
467
|
+
|
|
468
|
+
Resolve vessel attribute IDs (propulsion, scrubber type, ice class) to human-readable labels.
|
|
469
|
+
|
|
470
|
+
```python
|
|
471
|
+
from vortexasdk import Attributes
|
|
472
|
+
|
|
473
|
+
# Get all scrubber types
|
|
474
|
+
df = Attributes().search(type="scrubber").to_df()
|
|
475
|
+
|
|
476
|
+
# Resolve specific attribute IDs from vessel data
|
|
477
|
+
propulsion_id = "3ace0e050724707b"
|
|
478
|
+
attr = Attributes().search(ids=[propulsion_id]).to_df()
|
|
479
|
+
# Returns: id, name="DFDE", type="propulsion"
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**search() parameters:**
|
|
483
|
+
|
|
484
|
+
| Parameter | Type | Description |
|
|
485
|
+
|---|---|---|
|
|
486
|
+
| `type` | str | One of: `ice_class`, `propulsion`, `scrubber` |
|
|
487
|
+
| `term` | str or List[str] | Attribute name to search |
|
|
488
|
+
| `ids` | str or List[str] | Specific attribute IDs to resolve |
|
|
489
|
+
|
|
490
|
+
### Asset Tanks
|
|
491
|
+
|
|
492
|
+
Search onshore oil storage tanks.
|
|
493
|
+
|
|
494
|
+
```python
|
|
495
|
+
from vortexasdk import AssetTanks
|
|
496
|
+
|
|
497
|
+
df = AssetTanks().search(storage_type=["refinery"]).to_df()
|
|
498
|
+
df = AssetTanks().search(location_ids=["<geo_id>"], crude_confidence=["confirmed"]).to_df()
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
**Key parameters:** `storage_type` (refinery/commercial/spr), `crude_confidence` (confirmed/probable/unlikely), `location_ids`, `corporate_entity_ids`
|
|
502
|
+
|
|
503
|
+
### Storage Terminals
|
|
504
|
+
|
|
505
|
+
```python
|
|
506
|
+
from vortexasdk import StorageTerminals
|
|
507
|
+
|
|
508
|
+
df = StorageTerminals().search(term=["Military"]).to_df()
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### Refineries
|
|
512
|
+
|
|
513
|
+
```python
|
|
514
|
+
from vortexasdk import Refineries
|
|
515
|
+
|
|
516
|
+
df = Refineries().search(term="San").to_df(columns=["name", "country_name"])
|
|
517
|
+
df = Refineries().search(status="Active").to_df(columns="all")
|
|
518
|
+
df = Refineries().search(owner_id=["<corporate_id>"]).to_df()
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
**Key parameters:** `status` (Active/Shut/Unknown/Upcoming), `owner_id`, `operator_id`, `refinery_name`
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## Worked Examples
|
|
526
|
+
|
|
527
|
+
### 1. Resolve geography IDs for use in cargo/voyage queries
|
|
528
|
+
|
|
529
|
+
```python
|
|
530
|
+
from vortexasdk import Geographies
|
|
531
|
+
|
|
532
|
+
targets = {"China": "country", "Singapore": "country", "Rotterdam": "port"}
|
|
533
|
+
ids = {}
|
|
534
|
+
for name, layer in targets.items():
|
|
535
|
+
result = Geographies().search(term=name, exact_term_match=True, filter_layer=layer).to_df()
|
|
536
|
+
if not result.empty:
|
|
537
|
+
ids[name] = result.iloc[0]["id"]
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### 2. Get all crude grades under a category
|
|
541
|
+
|
|
542
|
+
```python
|
|
543
|
+
from vortexasdk import Products
|
|
544
|
+
|
|
545
|
+
# First find Medium-Sour category
|
|
546
|
+
category = Products().search(term="Medium-Sour", exact_term_match=True, filter_layer="category").to_df()
|
|
547
|
+
parent_id = category.iloc[0]["id"]
|
|
548
|
+
|
|
549
|
+
# Get all grades under it
|
|
550
|
+
grades = Products().search(product_parent=parent_id, filter_layer="grade").to_df()
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### 3. Find all VLCCs owned by a specific company
|
|
554
|
+
|
|
555
|
+
```python
|
|
556
|
+
from vortexasdk import Corporations, Vessels
|
|
557
|
+
|
|
558
|
+
corp = Corporations().search(term="BAHRI", exact_term_match=True).to_df()
|
|
559
|
+
vlccs = Vessels().search(vessel_classes="oil_vlcc").to_df(columns="all")
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### 4. Look up vessel by IMO number
|
|
563
|
+
|
|
564
|
+
```python
|
|
565
|
+
from vortexasdk import Vessels
|
|
566
|
+
|
|
567
|
+
df = Vessels().search(ids=[9779836]).to_df(columns=["id", "name", "imo", "mmsi", "dwt", "vessel_class"])
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### 5. Get all active refineries
|
|
571
|
+
|
|
572
|
+
```python
|
|
573
|
+
from vortexasdk import Refineries
|
|
574
|
+
|
|
575
|
+
df = Refineries().search(status="Active").to_df(columns="all")
|
|
576
|
+
# Filter by country_name in the DataFrame
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### 6. Resolve attribute IDs from vessel data
|
|
580
|
+
|
|
581
|
+
```python
|
|
582
|
+
from vortexasdk import Attributes
|
|
583
|
+
|
|
584
|
+
propulsion_id = "3ace0e050724707b"
|
|
585
|
+
attr = Attributes().search(ids=[propulsion_id]).to_df()
|
|
586
|
+
# Returns: id, name="DFDE", type="propulsion"
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
## Error Recovery
|
|
592
|
+
|
|
593
|
+
| Problem | Cause | Fix |
|
|
594
|
+
|---|---|---|
|
|
595
|
+
| Only 1 result returned | Default `size` is 1 | Set `size=500` or higher |
|
|
596
|
+
| No results for a known entity | Name differs from Vortexa label | Try partial name, remove special characters, set `exact_term_match=False` |
|
|
597
|
+
| SDK `Corporations` doesn't filter by entity type | SDK doesn't expose `corporateEntityType` | Use REST API `/v5/reference/charterers` directly |
|
|
598
|
+
| Geography search returns too many results | Common name matches many entries | Add `filter_layer` to narrow results |
|
|
599
|
+
| Vessel search by IMO returns nothing | IMO passed as string instead of integer | Pass IMO as integer in the `ids` or `term` array |
|
|
600
|
+
| Product hierarchy unclear | Don't know which layer a product belongs to | Search without `filter_layer`, check the `layer` field in results |
|
|
601
|
+
| Corporate entity ID doesn't match search | Response uses 16-char truncated IDs | Use the 64-char ID from the entity's `id` field, or search by name |
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
## ID Formats
|
|
606
|
+
|
|
607
|
+
| Format | Length | Used In | Example |
|
|
608
|
+
|---|---|---|---|
|
|
609
|
+
| Full hex ID | 64 characters | All API filter parameters, entity lookups | `a1b2c3d4e5f6...` (64 chars) |
|
|
610
|
+
| Truncated hex ID | 16 characters | `cargo_movement_id` in joins, voyage links, `vessels-ids` with `return_type: "short"` | `a1b2c3d4e5f6a7b8` |
|
|
611
|
+
|
|
612
|
+
**ALWAYS** use full 64-char IDs for API filter parameters. Truncated 16-char IDs appear in cross-endpoint joins (e.g. matching cargo movements to voyages) but are not valid for filter parameters.
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
## Defaults and Pagination
|
|
617
|
+
|
|
618
|
+
| Parameter | Default | Notes |
|
|
619
|
+
|---|---|---|
|
|
620
|
+
| `size` | 1 | **Always set explicitly.** Default returns only 1 record. Max: 10,000 |
|
|
621
|
+
| `search_after` | -- | Use `next_request.search_after` from previous response for pagination |
|
|
622
|
+
| `exact_term_match` (SDK) | `False` | Fuzzy matching by default |
|
|
623
|
+
|
|
624
|
+
**Pagination pattern (REST):**
|
|
625
|
+
|
|
626
|
+
```python
|
|
627
|
+
import requests
|
|
628
|
+
|
|
629
|
+
all_data = []
|
|
630
|
+
payload = {"term": "crude", "filter_layer": ["grade"], "size": 500}
|
|
631
|
+
|
|
632
|
+
while True:
|
|
633
|
+
resp = requests.post(f"{base}/v5/reference/products", headers=headers, json=payload)
|
|
634
|
+
result = resp.json()
|
|
635
|
+
all_data.extend(result["data"])
|
|
636
|
+
if "next_request" not in result or not result["next_request"]:
|
|
637
|
+
break
|
|
638
|
+
payload["search_after"] = result["next_request"]["search_after"]
|
|
639
|
+
payload["size"] = result["next_request"]["size"]
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
**Common API response structure:**
|
|
643
|
+
|
|
644
|
+
```json
|
|
645
|
+
{
|
|
646
|
+
"total": 150,
|
|
647
|
+
"data": [<records>],
|
|
648
|
+
"metadata": [...],
|
|
649
|
+
"next_request": {"size": 500, "search_after": {...}}
|
|
650
|
+
}
|
|
651
|
+
```
|