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.
@@ -0,0 +1,636 @@
1
+ # Voyages & VoyagesSearchEnriched
2
+
3
+ > Endpoint documentation for querying vessel voyages, laden/ballast periods, events, and location enrichment.
4
+ > For date/unit rules, see date-units.md (pre-loaded). For entity resolution, see entity-resolution.md (pre-loaded).
5
+
6
+ ---
7
+
8
+ ## When to Use Voyages vs CargoMovements
9
+
10
+ This is the most important decision. Getting it wrong means querying the wrong endpoint entirely.
11
+
12
+ | User Wants | Use This | Why |
13
+ |---|---|---|
14
+ | Product-centric flow volumes (exports, imports, trade flows) | CargoMovements / CargoTimeSeries | CM tracks cargo quantity from origin to destination |
15
+ | Vessel-centric voyage tracking (where did this ship go?) | VoyagesSearchEnriched | Voyages tracks the vessel journey with events |
16
+ | OOW volumes broken down by region | CM for volumes + Voyages for location | CM-Authoritative pattern (see below) |
17
+ | Individual vessel journey details | VoyagesSearchEnriched | Full voyage with events, ports, durations |
18
+ | Congestion / waiting time analysis | Congestion breakdown or VoyagesSearchEnriched | Location + waiting time data |
19
+ | Aggregate vessel counts or DWT over time | VoyagesTimeseries | Time-series vessel-perspective metrics |
20
+ | Tonne-miles, voyage counts with corporate splits | VoyagesTimeseriesV2 | V2 has richer corporate breakdowns |
21
+ | Fleet utilisation, ballast positioning | VoyagesTimeseries or VoyagesSearchEnriched | Voyage status + vessel class filters |
22
+ | "How much crude flowed from A to B?" | CargoMovements / CargoTimeSeries | NEVER use Voyages for volume flow questions |
23
+ | "How many tankers arrived at port X?" | VoyagesTimeseries with `arrivals` | Vessel count, not cargo volume |
24
+
25
+ **Signal words for Voyages:** "vessels", "ships", "voyages", "fleet", "tonne-miles", "DWT", "congestion", "ballast", "laden", "voyage events", "vessel journey", "speed", "waiting time"
26
+
27
+ **Signal words for CargoMovements:** "exports", "imports", "barrels", "tonnes", "cargo", "trade flow", "product flow", "volumes shipped", "floating storage volumes"
28
+
29
+ ---
30
+
31
+ ## CRITICAL RULES
32
+
33
+ **NEVER** mix V1 underscore naming with V2 hyphen naming:
34
+ - V1: `origin_country`, `vessel_count`, `tonne_miles`
35
+ - V2: `origin-country`, `voyage-count`, `tonne-miles-sum`
36
+ - V2 metric and breakdown are constructor args; V1 they are search params
37
+
38
+ **NEVER** forget the `exclude_overlapping_entries` behavior difference:
39
+ - Timeseries: defaults to `true` (de-duplicates overlapping voyage periods)
40
+ - Search: defaults to `false` (may count overlapping days)
41
+
42
+ **NEVER** assume ballast voyage origin = vessel's starting port:
43
+ - For ballast voyages, `origins` = last discharge port (not where the ballast voyage started)
44
+ - For ballast voyages, `destinations` = next load port (not where the ballast voyage ends)
45
+ - This is the opposite of laden voyage logic and a common source of confusion
46
+
47
+ **ALWAYS** specify `voyage_status` filter -- default returns all statuses including outdated ones.
48
+
49
+ **ALWAYS** set `size` higher than 1 for search endpoints -- the default is `1`, which returns only a single record.
50
+
51
+ **ALWAYS** set `time_min` and `time_max` explicitly -- never rely on defaults.
52
+
53
+ **ALWAYS** match `voyage_date_range_activity` to user intent:
54
+
55
+ | User Says | `voyage_date_range_activity` | Meaning |
56
+ |---|---|---|
57
+ | "active vessels", "vessels during this period" | `active` | Voyage overlapped the time window at any point |
58
+ | "arrivals", "vessels that arrived" | `arrivals` | Voyage ended (final discharge) within the time window |
59
+ | "departures", "vessels that departed" | `departures` | Voyage started (first load) within the time window |
60
+ | "transited waypoint" | `waypoint` | Had a waypoint event within the time window |
61
+
62
+ See also: guardrails.md for cross-cutting rules on entity resolution, date ranges, and confirmation steps.
63
+
64
+ ---
65
+
66
+ ## Endpoint Selection
67
+
68
+ | Endpoint | Purpose | Returns | When to Use |
69
+ |---|---|---|---|
70
+ | VoyagesSearchEnriched | Individual voyage details | List of voyage objects with events | "show voyages", "vessel journey", "list ships" |
71
+ | VoyagesTimeseries (V1) | Aggregate vessel metrics over time | Time series with breakdowns | "how many vessels", "DWT trend", "fleet utilisation" |
72
+ | VoyagesTimeseriesV2 | Voyage-level metric aggregations | Time series with V2 splits | "tonne-miles by class", "voyage count by corporate" |
73
+ | Congestion breakdown | Congestion counts by location | Port/terminal congestion data | "port congestion", "waiting times" |
74
+ | Top hits | Voyage counts by property | Ranked list | "busiest ports", "top destinations" |
75
+
76
+ ---
77
+
78
+ ## What is a Voyage
79
+
80
+ A voyage is a **continuous laden or ballast period** for a single vessel. Each voyage contains multiple **Voyage Events** describing activity during that period.
81
+
82
+ **Key rules:**
83
+ - A laden voyage starts at first load, ends at full discharge (partial discharges do not end a voyage)
84
+ - A ballast voyage starts after full discharge, ends before next load
85
+ - Multiple loads/discharges within one continuous period = still one voyage
86
+ - STS transfers, congestion/waiting, and floating storage are recorded as events within the voyage, not as separate voyages
87
+ - Congestion events include duration and location, extractable from voyage events
88
+
89
+ ---
90
+
91
+ ## Status Filters
92
+
93
+ Each status filter has a corresponding `*_excluded` variant for exclusion.
94
+
95
+ ### Voyage Status
96
+
97
+ | Value | Meaning | When to Use |
98
+ |---|---|---|
99
+ | `laden` | Vessel is carrying cargo | "loaded ships", "carrying cargo" |
100
+ | `ballast` | Vessel is empty / repositioning | "empty vessels", "ballast fleet" |
101
+
102
+ ### Movement Status
103
+
104
+ | Value | Meaning | When to Use |
105
+ |---|---|---|
106
+ | `moving` | Vessel underway | "in transit", "moving vessels" |
107
+ | `stationary` | Vessel stopped | "anchored", "stopped" |
108
+ | `waiting` | Vessel queuing | "waiting vessels", "queuing" |
109
+ | `congestion` | Vessel in congestion zone | "congested" |
110
+ | `slow` | Vessel slow steaming | "slow steaming" |
111
+
112
+ ### Cargo Status
113
+
114
+ | Value | Meaning | When to Use |
115
+ |---|---|---|
116
+ | `in-transit` | Cargo underway | "in transit cargo" |
117
+ | `loading` | Cargo being loaded | "at load port" |
118
+ | `discharging` | Cargo being discharged | "at discharge port" |
119
+ | `floating-storage` | Cargo in floating storage | "stored at sea" |
120
+
121
+ ### Location Status
122
+
123
+ | Value | Meaning | When to Use |
124
+ |---|---|---|
125
+ | `berth` | At berth / docked | "docked" |
126
+ | `anchorage-zone` | At anchorage | "anchored" |
127
+ | `on-the-sea` | At sea | "on the water" |
128
+ | `dry-dock` | In dry dock | "in dry dock" |
129
+
130
+ ### Commitment Status
131
+
132
+ | Value | Meaning | When to Use |
133
+ |---|---|---|
134
+ | `committed` | Vessel booked / fixed | "committed", "fixed" |
135
+ | `uncommitted` | Vessel not committed | "open", "available" |
136
+ | `open` | Vessel open for charter | "open" |
137
+ | `unknown` | Status unknown | -- |
138
+
139
+ ### Vessel Risk Level
140
+
141
+ | Value | When to Use |
142
+ |---|---|
143
+ | `low` | "clean vessels", "low risk" |
144
+ | `medium` | "medium risk" |
145
+ | `high` | "high risk", "risky vessels" |
146
+ | `sanctioned` | "sanctioned fleet" |
147
+
148
+ ---
149
+
150
+ ## Geography Filters
151
+
152
+ All geography filters require Vortexa hex hash IDs (see entity-resolution.md). Each has a `*_excluded` variant.
153
+
154
+ | Parameter | Filters On | Use When User Says |
155
+ |---|---|---|
156
+ | `origins` | Cargo origin(s); for ballast = last discharge port | "departing from", "loaded in" |
157
+ | `destinations` | Cargo destination(s); for ballast = next load port | "heading to", "discharging at" |
158
+ | `locations` | Any visited location during the voyage | "passing through", "currently at" |
159
+ | `voyage_origins` | First port visit of the voyage | "voyage started from" |
160
+ | `voyage_destinations` | Last port visit of the voyage | "voyage ended at" |
161
+ | `voyage_routes` | First origin OR final destination matches | "route involves" |
162
+ | `congestion_target_location` | Location where congestion occurred | "congested at" |
163
+
164
+ ---
165
+
166
+ ## API Reference: VoyagesSearchEnriched
167
+
168
+ **Endpoint:** `POST /v5/voyages/search-enriched`
169
+ **SDK:** `VoyagesSearchEnriched().search(...)`
170
+ **Purpose:** Retrieve individual voyage records with full event, vessel, product, and corporate detail.
171
+
172
+ ### Endpoint-Specific Parameters
173
+
174
+ | Parameter | Type | Default | Description |
175
+ |---|---|---|---|
176
+ | `size` | integer | `1` | Records per page (0-500). **Default is 1 -- always set higher.** |
177
+ | `unit` | enum | `b` | Quantity unit: `b` (barrels), `t` (tonnes), `cbm` (cubic metres) |
178
+ | `order` | enum | `vessel_name` | Sort by: `vessel_name`, `dwt`, `vessel_class`, `start_date`, `end_date`, `latest_product`, `tonne_miles`, `distance`, `duration`, `quantity`, `quantity_tonnes`, `quantity_barrels`, `quantity_cubic_metres` |
179
+ | `order_direction` | enum | `desc` | `asc` or `desc` |
180
+ | `search_after` | object | none | Pagination cursor from previous response's `next_request` |
181
+ | `_response` | enum | `json` | `json` or `csv` |
182
+
183
+ ### SDK-Specific Parameters
184
+
185
+ | Parameter | Description |
186
+ |---|---|
187
+ | `columns` | `None` for `.to_list()`, `'all'` or list of column names for `.to_df()` |
188
+ | `origin_behaviour` | Which departure mode `voyage_date_range_activity` counts: `first_load` or `any_load` |
189
+ | `destination_behaviour` | Which arrival mode counts: `last_discharge` or `any_discharge` |
190
+ | `flags` | SDK uses `flags` instead of REST `vessel_flags` |
191
+ | `ice_class` | SDK uses `ice_class` instead of REST `vessel_ice_class` |
192
+ | `vessel_propulsion` | Filter by propulsion type IDs |
193
+
194
+ ### DataFrame Columns
195
+
196
+ Available columns for `.to_df()` and CSV output:
197
+
198
+ **Vessel:** `vessel_name`, `imo`, `dwt`, `capacity`, `vessel_class`, `build_year`, `flag`, `risk_rating`, `scrubber`, `coating`
199
+
200
+ **Voyage:** `voyage_status`, `cargo_status`, `start_date`, `end_date`, `duration`, `distance`, `tonne_miles`, `quantity`, `voyage_id`, `previous_voyage_id`, `next_voyage_id`
201
+
202
+ **Origin (cargo origin):** `origin`, `origin_terminal`, `origin_port`, `origin_country`, `origin_shipping_region`, `origin_basin`, `origin_wider_shipping_region`, `origin_country_zone`, `origin_alternative_region`, `origin_state_or_province`
203
+
204
+ **Destination (cargo destination):** `destination`, `destination_terminal`, `destination_port`, `destination_country`, `destination_shipping_region`, `destination_basin`, `destination_wider_shipping_region`, `destination_country_zone`, `destination_alternative_region`, `destination_state_or_province`
205
+
206
+ **First/Final (voyage-level):** `first_origin`, `first_origin_terminal`, `first_origin_port`, `first_origin_country`, `first_origin_shipping_region` (and same pattern with `final_destination_*`)
207
+
208
+ **Commercial:** `charterer`, `effective_controller`, `time_charterer`
209
+
210
+ **Product:** `latest_product`, `latest_product_group`, `latest_product_category`, `latest_product_grade`
211
+
212
+ ### CSV Output
213
+
214
+ Request CSV by setting `_response='csv'` or header `accept: text/csv`, with `csv_columns='all'` or a list of specific columns.
215
+
216
+ ---
217
+
218
+ ## API Reference: VoyagesTimeseries (V1)
219
+
220
+ **Endpoint:** `POST /v5/voyages/timeseries`
221
+ **SDK:** `VoyagesTimeseries().search(...)`
222
+ **Purpose:** Time-series vessel data. Aggregate metrics like vessel count, cargo quantity, tonne-miles, DWT, speed, wait times with breakdowns.
223
+
224
+ ### Endpoint-Specific Parameters
225
+
226
+ | Parameter | Type | Default | Description |
227
+ |---|---|---|---|
228
+ | `breakdown_frequency` | enum | `day` | `day`, `week`, `doe_week`, `month`, `quarter`, `year` |
229
+ | `breakdown_property` | enum | `vessel_count` | What to measure: `vessel_count`, `utilisation`, `cargo_quantity`, `avg_wait_time`, `dwt`, `cubic_capacity`, `tonne_miles`, `avg_distance`, `avg_speed` |
230
+ | `breakdown_split_property` | enum | `none` | How to split. See split values below. |
231
+ | `breakdown_unit_operator` | enum | `sum` | `sum` or `avg` |
232
+ | `breakdown_size` | integer | `100` | Max breakdown categories (0-10000). Overflow grouped under "Other". |
233
+ | `exclude_overlapping_entries` | boolean | `true` | De-duplicate overlapping voyage periods. **Defaults true for timeseries.** |
234
+
235
+ ### Split Property Values (V1)
236
+
237
+ **Vessel:** `vessel_status`, `vessel_class_group`, `vessel_class_coarse`, `vessel_class_granular`, `vessel_flag`, `commitment_status`, `fixture_status`, `cargo_status`, `location_status`, `movement_status`, `presets`
238
+
239
+ **Corporate:** `effective_controller`, `charterer`
240
+
241
+ **Origin geography:** `origin_region`, `origin_shipping_region`, `origin_shipping_region_v2`, `origin_wider_shipping_region`, `origin_basin`, `origin_trading_region`, `origin_trading_sub_region`, `origin_trading_block`, `origin_country`, `origin_country_zone`, `origin_port`, `origin_port_or_sts_zone`, `origin_terminal`, `origin_alternative_region`, `origin_state_or_province`
242
+
243
+ **Destination geography:** `destination_region`, `destination_shipping_region`, `destination_shipping_region_v2`, `destination_wider_shipping_region`, `destination_basin`, `destination_trading_region`, `destination_trading_sub_region`, `destination_trading_block`, `destination_country`, `destination_country_zone`, `destination_port`, `destination_port_or_sts_zone`, `destination_terminal`, `destination_alternative_region`, `destination_state_or_province`
244
+
245
+ **Location (current):** `location_port`, `location_country`, `location_shipping_region`, `location_shipping_region_v2`, `location_wider_shipping_region`, `location_basin`, `location_country_zone`
246
+
247
+ **Congestion location:** `congestion_location_port`, `congestion_location_country`, `congestion_location_shipping_region`, `congestion_location_shipping_region_v2`, `congestion_location_basin`, `congestion_location_wider_shipping_region`, `congestion_location_region`, `congestion_location_trading_region`, `congestion_location_trading_sub_region`, `congestion_location_trading_block`, `congestion_location_alternative_region`, `congestion_location_country_zone`, `congestion_location_state_or_province`
248
+
249
+ **Product:** `product_group`, `product_group_product`, `product_category`, `product_grade`, `latest_product_group`, `latest_product_group_product`, `latest_product_category`, `latest_product_grade`
250
+
251
+ ### Response Format
252
+
253
+ Each row contains `key` (timestamp), `value` (aggregate), `count`, and a `breakdown` array of `{id, label, value, count}` pairs for each split category.
254
+
255
+ ---
256
+
257
+ ## API Reference: VoyagesTimeseriesV2
258
+
259
+ **Endpoint:** `POST /v5/voyages/timeseries/{metric}/{breakdown_property}`
260
+ **SDK:** `VoyagesTimeseriesV2(metric, breakdown_property).search(...)`
261
+ **Purpose:** Voyage-level metric aggregations with metric and breakdown set at instantiation (SDK) or in URL path (REST).
262
+
263
+ ### Constructor / Path Parameters
264
+
265
+ | Parameter | Values |
266
+ |---|---|
267
+ | `metric` | `voyage-count`, `voyage-count-per-day`, `tonne-miles-sum`, `tonne-miles-sum-per-day`, `distance-avg`, `distance-voyage-avg`, `duration-avg` |
268
+ | `breakdown_property` | `status`, `vessel-class-group`, `vessel-class-coarse`, `vessel-class-granular`, `origin-region`, `origin-shipping-region`, `origin-wider-shipping-region`, `origin-trading-region`, `origin-basin`, `origin-country`, `origin-port`, `origin-terminal`, `destination-region`, `destination-shipping-region`, `destination-wider-shipping-region`, `destination-trading-region`, `destination-basin`, `destination-country`, `destination-port`, `destination-terminal`, `location-waypoint`, `product-group`, `product-group-product`, `product-category`, `product-grade`, `corporate-entity-cargo-trader`, `corporate-entity-charterer`, `corporate-entity-effective-controller`, `corporate-entity-vessel-owner`, `corporate-entity-time-charterer` |
269
+
270
+ ### Search Parameters
271
+
272
+ | Parameter | Type | Default | Description |
273
+ |---|---|---|---|
274
+ | `breakdown_frequency` | enum | `day` | `day`, `week`, `doe_week`, `month`, `quarter`, `year` |
275
+ | `breakdown_size` | integer | `100` | Max breakdown categories |
276
+ | `exclude_overlapping_entries` | boolean | `true` | De-duplicate overlapping voyages |
277
+
278
+ All shared filters apply.
279
+
280
+ ---
281
+
282
+ ## API Reference: Top Hits
283
+
284
+ **Endpoint:** `POST /v5/voyages/top-hits`
285
+ **Purpose:** Count of voyages aggregated by a chosen property. Use for "busiest port", "top destination" rankings.
286
+
287
+ | Parameter | Type | Description |
288
+ |---|---|---|
289
+ | `breakdown_size` | integer | Number of top results |
290
+ | `breakdown_property` | enum | Fixed: `vessel_count` |
291
+ | `breakdown_split_property` | enum | Same options as V1 timeseries split |
292
+
293
+ ---
294
+
295
+ ## API Reference: Congestion Breakdown
296
+
297
+ **Endpoint:** `POST /v5/voyages/congestion-breakdown`
298
+ **Purpose:** Count of congested voyages by location. Use for port congestion analysis.
299
+
300
+ | Parameter | Type | Description |
301
+ |---|---|---|
302
+ | `breakdown_size` | integer | Number of results |
303
+ | `breakdown_property` | enum | `port`, `terminal`, or `shipping_region` |
304
+ | `order` | enum | `location`, `avg_wait`, `dwt`, `capacity`, `count` |
305
+ | `order_direction` | enum | `asc` or `desc` |
306
+
307
+ ---
308
+
309
+ ## V1 vs V2 Timeseries: When to Use Which
310
+
311
+ | Aspect | V1 (`VoyagesTimeseries`) | V2 (`VoyagesTimeseriesV2`) |
312
+ |---|---|---|
313
+ | Architecture | Single endpoint, property + split params | Metric and breakdown in constructor/URL |
314
+ | Naming convention | Underscores (`origin_country`) | Hyphens (`origin-country`) |
315
+ | Metrics | `vessel_count`, `cargo_quantity`, `avg_wait_time`, `dwt`, `cubic_capacity`, `tonne_miles`, `avg_distance`, `avg_speed`, `utilisation` | `voyage-count`, `voyage-count-per-day`, `tonne-miles-sum`, `tonne-miles-sum-per-day`, `distance-avg`, `distance-voyage-avg`, `duration-avg` |
316
+ | Corporate splits | `charterer`, `effective_controller` only | Full corporate: `corporate-entity-cargo-trader`, `-charterer`, `-effective-controller`, `-vessel-owner`, `-time-charterer` |
317
+ | Vessel owner/TC filters | Not in V1 SDK `search()` | `vessel_owners`, `time_charterer` available |
318
+ | Use case | General vessel analytics, fleet metrics (DWT, speed, wait time, utilisation) | Voyage-level metrics, corporate breakdowns, per-day rates |
319
+
320
+ **Rule of thumb:** Use V1 for vessel-perspective metrics (DWT, speed, wait time, utilisation). Use V2 for voyage-perspective metrics (voyage counts, tonne-miles sums, duration averages) especially when you need corporate entity breakdowns or per-day rates.
321
+
322
+ ---
323
+
324
+ ## Shared Filter Reference
325
+
326
+ ### Time & ID Filters
327
+
328
+ | Parameter | Type | Description |
329
+ |---|---|---|
330
+ | `time_min` | ISO 8601 | Start of time range. **Always specify explicitly.** |
331
+ | `time_max` | ISO 8601 | End of time range. **Always specify explicitly.** |
332
+ | `voyage_id` | string[] | Filter by specific voyage IDs |
333
+ | `cargo_movement_id` | string[] | Filter by related cargo movement IDs |
334
+
335
+ ### Product Filters
336
+
337
+ | Parameter | Description |
338
+ |---|---|
339
+ | `products` | Product IDs in the voyage's cargo |
340
+ | `latest_products` | Product IDs of the latest cargo (current voyage if laden, previous if ballast) |
341
+
342
+ ### Corporate Filters
343
+
344
+ | Parameter | Description |
345
+ |---|---|
346
+ | `charterers` | Charterer corporation IDs |
347
+ | `effective_controllers` | Effective controller corporation IDs |
348
+ | `vessel_owners` | Vessel owner corporation IDs (REST) |
349
+ | `time_charterer` | Time charterer corporation IDs (REST) |
350
+
351
+ ### Vessel Filters
352
+
353
+ | Parameter | Type | Description |
354
+ |---|---|---|
355
+ | `vessels` | string[] | Vessel IDs OR vessel class strings (e.g., `oil_vlcc`, `lng_tfde_dfde`) |
356
+ | `vessel_flags` | string[] | Flag country IDs (REST) / `flags` (SDK) |
357
+ | `vessel_ice_class` | string[] | Ice class IDs (REST) / `ice_class` (SDK) |
358
+ | `vessel_propulsion` | string[] | Propulsion type IDs |
359
+ | `vessel_age_min` / `max` | number | Age in years (0-100) |
360
+ | `vessel_dwt_min` / `max` | number | Deadweight tonnage (0-550,000) |
361
+ | `vessel_cbm_min` / `max` | number | Cubic capacity (0-300,000) |
362
+ | `vessel_wait_time_min` / `max` | number | Days until vessel available |
363
+ | `vessel_scrubbers` | enum | `disabled` (default), `inc` (only with), `exc` (only without) |
364
+ | `vessel_risk_level` | enum[] | `low`, `medium`, `high`, `sanctioned` |
365
+ | `vessel_tags` | Tag[] | Time-bound vessel tag objects |
366
+ | `average_speed_min` / `max` | number | Avg speed in knots (0-100) |
367
+
368
+ ### Toggle Filters
369
+
370
+ | Parameter | Values | Description |
371
+ |---|---|---|
372
+ | `has_ship_to_ship` | `disabled`, `inc`, `exc` | Filter by STS occurrence |
373
+ | `has_charterer` | `disabled`, `inc`, `exc` | Filter by charterer presence |
374
+
375
+ ### Behaviour Filters
376
+
377
+ | Parameter | Values | Description |
378
+ |---|---|---|
379
+ | `voyage_date_range_activity` | `active`, `arrivals`, `departures`, `waypoint` | How time range interacts with voyages |
380
+ | `origin_behaviour` | `first_load`, `any_load` | Which load counts for departure activity (SDK) |
381
+ | `destination_behaviour` | `last_discharge`, `any_discharge` | Which discharge counts for arrival activity (SDK) |
382
+ | `intra_movements` | `all`, `exclude_intra_country`, `exclude_intra_geography` | Exclude domestic/intra-region voyages |
383
+ | `exclude_overlapping_entries` | boolean | De-duplicate overlapping voyages. **Timeseries: true; Search: false.** |
384
+ | `event_types` | `vessel`, `cargo`, `status` | Filter which event types are returned in voyage records |
385
+
386
+ ---
387
+
388
+ ## Worked Examples
389
+
390
+ ### Example 1: Laden VLCC voyages from MEG to Asia
391
+
392
+ **User:** "Show me all laden VLCC voyages from the Middle East to Asia this quarter"
393
+
394
+ **Analysis:**
395
+ - "laden" -> `voyage_status='laden'`
396
+ - "VLCC" -> `vessels=['oil_vlcc']`
397
+ - "from the Middle East" -> resolve MEG geography ID -> `origins`
398
+ - "to Asia" -> resolve Asia geography ID -> `destinations`
399
+ - "this quarter" -> `time_min`/`time_max` for current quarter (see date-units.md)
400
+ - Individual records -> VoyagesSearchEnriched
401
+
402
+ ```python
403
+ from vortexasdk import VoyagesSearchEnriched, Geographies
404
+ from datetime import datetime
405
+
406
+ meg = [g.id for g in Geographies().search("Middle East", filter_layer="region").to_list()]
407
+ asia = [g.id for g in Geographies().search("Asia", filter_layer="region").to_list()]
408
+
409
+ df = VoyagesSearchEnriched().search(
410
+ time_min=datetime(2026, 1, 1),
411
+ time_max=datetime(2026, 3, 31),
412
+ voyage_status="laden",
413
+ vessels=["oil_vlcc"],
414
+ origins=meg,
415
+ destinations=asia,
416
+ columns="all",
417
+ ).to_df()
418
+ ```
419
+
420
+ ### Example 2: Monthly vessel count by destination country
421
+
422
+ **User:** "How many tankers arrived at European ports monthly in 2025, split by country?"
423
+
424
+ **Analysis:**
425
+ - "how many tankers" -> VoyagesTimeseries, `breakdown_property='vessel_count'`
426
+ - "arrived" -> `voyage_date_range_activity='arrivals'`
427
+ - "European ports" -> resolve Europe region ID -> `destinations`
428
+ - "monthly" -> `breakdown_frequency='month'`
429
+ - "split by country" -> `breakdown_split_property='destination_country'`
430
+
431
+ ```python
432
+ from vortexasdk import VoyagesTimeseries, Geographies
433
+ from datetime import datetime
434
+
435
+ europe = [g.id for g in Geographies().search("Europe", filter_layer="region").to_list()]
436
+
437
+ df = VoyagesTimeseries().search(
438
+ time_min=datetime(2025, 1, 1),
439
+ time_max=datetime(2025, 12, 31),
440
+ destinations=europe,
441
+ voyage_date_range_activity="arrivals",
442
+ breakdown_frequency="month",
443
+ breakdown_property="vessel_count",
444
+ breakdown_split_property="destination_country",
445
+ ).to_df()
446
+ ```
447
+
448
+ ### Example 3: Tonne-miles by vessel class (V2)
449
+
450
+ **User:** "Show weekly tonne-miles for crude voyages in 2025, split by vessel class"
451
+
452
+ **Analysis:**
453
+ - "tonne-miles" -> metric `tonne-miles-sum` (V2 hyphen naming)
454
+ - "split by vessel class" -> `breakdown_property='vessel-class-granular'` (V2 hyphen naming)
455
+ - "crude" -> resolve Crude product ID -> `products`
456
+ - "weekly" -> `breakdown_frequency='week'`
457
+ - V2 for metric+split architecture
458
+
459
+ ```python
460
+ from vortexasdk import VoyagesTimeseriesV2, Products
461
+ from datetime import datetime
462
+
463
+ crude = [p.id for p in Products().search("crude").to_list() if p.name == "Crude & Condensates"]
464
+
465
+ df = VoyagesTimeseriesV2(
466
+ metric="tonne-miles-sum",
467
+ breakdown_property="vessel-class-granular",
468
+ ).search(
469
+ time_min=datetime(2025, 1, 1),
470
+ time_max=datetime(2025, 12, 31),
471
+ products=crude,
472
+ voyage_status="laden",
473
+ breakdown_frequency="week",
474
+ ).to_df()
475
+ ```
476
+
477
+ ### Example 4: Port congestion analysis
478
+
479
+ **User:** "Which ports have the worst congestion for LNG carriers right now?"
480
+
481
+ **Analysis:**
482
+ - "congestion" -> Congestion breakdown endpoint
483
+ - "worst" -> order by `avg_wait` descending
484
+ - "LNG carriers" -> `vessels=['lng']`
485
+ - "right now" -> last 7 days
486
+
487
+ ```python
488
+ import requests
489
+ from datetime import datetime, timedelta
490
+
491
+ headers = {"x-api-key": VORTEXA_API_KEY}
492
+ payload = {
493
+ "time_min": (datetime.now() - timedelta(days=7)).isoformat() + "Z",
494
+ "time_max": datetime.now().isoformat() + "Z",
495
+ "vessels": ["lng"],
496
+ "breakdown_property": "port",
497
+ "order": "avg_wait",
498
+ "order_direction": "desc",
499
+ "breakdown_size": 20,
500
+ }
501
+
502
+ response = requests.post(
503
+ "https://api.vortexa.com/v5/voyages/congestion-breakdown",
504
+ headers=headers,
505
+ json=payload,
506
+ )
507
+ data = response.json()
508
+ ```
509
+
510
+ ### Example 5: Ballast fleet positioning (supply indicator)
511
+
512
+ **User:** "How many ballast VLCCs are heading to the MEG this month?"
513
+
514
+ **Analysis:**
515
+ - "ballast" -> `voyage_status='ballast'`
516
+ - "VLCCs" -> `vessels=['oil_vlcc']`
517
+ - "heading to the MEG" -> resolve MEG ID -> `destinations` (for ballast, this means next load port)
518
+ - "this month" -> current month date range
519
+ - "how many" but wants vessel list -> VoyagesSearchEnriched for detail
520
+
521
+ ```python
522
+ from vortexasdk import VoyagesSearchEnriched, Geographies
523
+ from datetime import datetime
524
+
525
+ meg = [g.id for g in Geographies().search("Middle East", filter_layer="region").to_list()]
526
+
527
+ df = VoyagesSearchEnriched().search(
528
+ time_min=datetime(2026, 2, 1),
529
+ time_max=datetime(2026, 2, 28),
530
+ voyage_status="ballast",
531
+ vessels=["oil_vlcc"],
532
+ destinations=meg,
533
+ voyage_date_range_activity="active",
534
+ columns=["vessel_name", "imo", "dwt", "origin_country", "destination_country",
535
+ "start_date", "distance", "duration"],
536
+ ).to_df()
537
+ ```
538
+
539
+ ### Example 6: Fleet DWT utilisation over time with split
540
+
541
+ **User:** "Show me total laden DWT by shipping region for Suezmaxes, monthly"
542
+
543
+ **Analysis:**
544
+ - "DWT" -> `breakdown_property='dwt'` (V1 metric)
545
+ - "by shipping region" -> `breakdown_split_property='destination_shipping_region_v2'` (V1 underscore naming)
546
+ - "Suezmaxes" -> `vessels=['oil_suezmax']`
547
+ - "monthly" -> `breakdown_frequency='month'`
548
+ - V1 for DWT metric
549
+
550
+ ```python
551
+ from vortexasdk import VoyagesTimeseries
552
+ from datetime import datetime
553
+
554
+ df = VoyagesTimeseries().search(
555
+ time_min=datetime(2025, 1, 1),
556
+ time_max=datetime(2025, 12, 31),
557
+ voyage_status="laden",
558
+ vessels=["oil_suezmax"],
559
+ breakdown_frequency="month",
560
+ breakdown_property="dwt",
561
+ breakdown_split_property="destination_shipping_region_v2",
562
+ ).to_df()
563
+ ```
564
+
565
+ ---
566
+
567
+ ## CM-Authoritative OOW Pattern (Preview)
568
+
569
+ For accurate oil-on-water (OOW) volumes broken down by region, **CargoMovements provides authoritative volumes** while **VoyagesSearchEnriched provides location enrichment**.
570
+
571
+ **Why not just use Voyages for OOW?** Voyages tracks the vessel, not the cargo. A single vessel may carry multiple parcels, and cargo quantities are authoritative only in CargoMovements (0% variance from CargoTimeSeries).
572
+
573
+ **High-level pattern:**
574
+ 1. Query CargoMovements with `cargo_on_water_state` for authoritative OOW periods and quantities
575
+ 2. Query VoyagesSearchEnriched for vessel location data (shipping_region from events)
576
+ 3. Join on `cargo_movement_id` (16-char truncated) to enrich CM cargoes with voyage location
577
+ 4. Query location at end of day for the latest region assignment
578
+ 5. Full-day counting: if OOW period overlaps a day at all, count full quantity
579
+
580
+ **When a user asks "OOW by region":** Use this CM-Authoritative pattern. Do NOT use VoyagesTimeseries alone -- it will give vessel counts or DWT, not cargo volumes.
581
+
582
+ Full implementation details will be in the cargo-movements.md context document and the OOW skill.
583
+
584
+ ---
585
+
586
+ ## Defaults and Common Pitfalls
587
+
588
+ | Parameter | Default | Pitfall | Fix |
589
+ |---|---|---|---|
590
+ | `time_min/max` | Current timestamp | Silently returns only current data | Always specify explicitly |
591
+ | `size` (search) | `1` | Returns only 1 record | Set to 500 for batch retrieval |
592
+ | `voyage_date_range_activity` | `active` | Returns all voyages touching the window, may overcount | Use `arrivals`/`departures` for event-anchored counting |
593
+ | `exclude_overlapping_entries` | `true` (TS), `false` (search) | Double-counting in search, dedup in timeseries | Set explicitly based on intent |
594
+ | `breakdown_frequency` | `day` | Too granular for long ranges | Use `month` for >3 months |
595
+ | `breakdown_property` (V1) | `vessel_count` | Wrong metric silently | Override for DWT, tonne-miles, speed |
596
+ | `breakdown_split_property` | `none` | No breakdown returned | Set to split by geography, vessel class, etc. |
597
+ | `breakdown_size` | `100` | Categories beyond 100 grouped as "Other" | Increase for many-category splits |
598
+ | `unit` | `b` (barrels) | Wrong for LNG/LPG | Use `t` for LNG/LPG, `cbm` for LNG volume |
599
+ | `vessel_scrubbers` | `disabled` | No filter applied | Use `inc`/`exc` when scrubber status matters |
600
+ | `intra_movements` | `all` | Includes domestic voyages | Exclude for international-only analysis |
601
+
602
+ ---
603
+
604
+ ## Error Recovery
605
+
606
+ **Empty results?** Check in this order:
607
+ 1. Are entity IDs correct? Re-run geography/product search to verify (see entity-resolution.md)
608
+ 2. Is the time range reasonable? Voyages need non-trivial windows
609
+ 3. Is `voyage_date_range_activity` appropriate? `active` is broadest; `arrivals`/`departures` are narrower
610
+ 4. For search: is `size` still set to 1 (default)?
611
+ 5. Are status filters too restrictive? Remove filters one at a time
612
+ 6. Is `intra_movements` excluding data? Try `all` first
613
+
614
+ **Unexpected counts / double-counting?**
615
+ 1. Check `exclude_overlapping_entries` -- `false` can double-count days with overlapping voyages
616
+ 2. Verify `voyage_date_range_activity` matches intent -- `active` counts every voyage that touched the window, not just those that started/ended in it
617
+ 3. For ballast voyages, `origins` means the last discharge port and `destinations` means the next load port -- this is inverted from laden logic
618
+
619
+ **V1 vs V2 confusion?**
620
+ - V2 uses hyphens (`tonne-miles-sum`, `origin-country`); V1 uses underscores (`tonne_miles`, `origin_country`)
621
+ - V2 metric + breakdown are constructor args; V1 they are search params
622
+ - Mixing naming conventions will silently fail or return errors
623
+
624
+ ---
625
+
626
+ ## Validation Rules
627
+
628
+ 1. `time_min` must be before `time_max`
629
+ 2. `size` for search defaults to 1 -- set to 500 for batch retrieval
630
+ 3. All ID filters require valid Vortexa hex hash IDs (see entity-resolution.md)
631
+ 4. `vessel_age_min/max`: 0-100 years
632
+ 5. `vessel_dwt_min/max`: 0-550,000 tonnes
633
+ 6. `vessel_cbm_min/max`: 0-300,000 cubic metres
634
+ 7. `average_speed_min/max`: 0-100 knots
635
+ 8. For pagination, use `search_after` from previous response's `next_request`
636
+ 9. `breakdown_size` max is 10,000
@@ -0,0 +1,4 @@
1
+ """Vortexa Analytics -- Python function library for commodity/energy data."""
2
+ __version__ = "0.1.0"
3
+
4
+ from lib.entities import resolve, disambiguate, EntityCache