From eb082b951db1ca7e253e5b6448ea504e16222f91 Mon Sep 17 00:00:00 2001 From: Alex Spedding Date: Wed, 26 Aug 2026 16:30:19 +0100 Subject: [PATCH 1/6] feat(rnd-22989): add VoyageCalculator endpoint Implements the Python SDK wrapper for POST /v5/voyages/voyage-calculator, which calculates voyage routes, ETAs, ETDs, or speeds between an origin and destination using the pathfinder routing engine. Co-Authored-By: Claude Opus 4.6 --- docs/endpoints/about-endpoints.md | 1 + .../try_me_out/voyage_calculator.ipynb | 133 ++++++++++++++++ tests/endpoints/test_voyage_calculator.py | 132 ++++++++++++++++ vortexasdk/__init__.py | 8 + vortexasdk/endpoints/__init__.py | 11 ++ vortexasdk/endpoints/endpoints.py | 2 + vortexasdk/endpoints/voyage_calculator.py | 149 ++++++++++++++++++ .../endpoints/voyage_calculator_result.py | 44 ++++++ 8 files changed, 480 insertions(+) create mode 100644 docs/examples/try_me_out/voyage_calculator.ipynb create mode 100644 tests/endpoints/test_voyage_calculator.py create mode 100644 vortexasdk/endpoints/voyage_calculator.py create mode 100644 vortexasdk/endpoints/voyage_calculator_result.py diff --git a/docs/endpoints/about-endpoints.md b/docs/endpoints/about-endpoints.md index bcf23fa9..f5f5b635 100644 --- a/docs/endpoints/about-endpoints.md +++ b/docs/endpoints/about-endpoints.md @@ -21,6 +21,7 @@ The VortexaSDK currently contains the following endpoints: 1. Canal transit 1. Canal transit Time Series 1. Refineries +1. Voyage Calculator Each endpoint offers either one, or both, of two different functionalities: diff --git a/docs/examples/try_me_out/voyage_calculator.ipynb b/docs/examples/try_me_out/voyage_calculator.ipynb new file mode 100644 index 00000000..b23a9568 --- /dev/null +++ b/docs/examples/try_me_out/voyage_calculator.ipynb @@ -0,0 +1,133 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cell-0", + "metadata": {}, + "source": [ + "### Try out the VortexaSDK" + ] + }, + { + "cell_type": "markdown", + "id": "cell-1", + "metadata": {}, + "source": [ + "First let's import our requirements" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-2", + "metadata": {}, + "outputs": [], + "source": [ + "from vortexasdk import VoyageCalculator" + ] + }, + { + "cell_type": "markdown", + "id": "cell-3", + "metadata": {}, + "source": [ + "You'll need to enter your Vortexa API key when prompted." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-4", + "metadata": {}, + "outputs": [], + "source": [ + "# Calculate ETA for a VLCC travelling from Ras Tanura to Rotterdam at 12 knots\n", + "ras_tanura = \"006bca77c1390ad4daec7e1bff40e6560583be7e4c2caab7cc641db1ae69dd9b\"\n", + "rotterdam = \"68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e\"\n", + "\n", + "result = VoyageCalculator().search(\n", + " type=\"ETA\",\n", + " vessel_status=\"vessel_status_laden_known\",\n", + " origin=ras_tanura,\n", + " destination=rotterdam,\n", + " vessel_class=\"oil_vlcc\",\n", + " ETD=\"2024-03-01T00:00:00.000Z\",\n", + " speed=12,\n", + ")\n", + "df = result.to_df()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-5", + "metadata": {}, + "outputs": [], + "source": [ + "df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-6", + "metadata": {}, + "outputs": [], + "source": [ + "# Calculate speed required to arrive by a specific date\n", + "speed_result = VoyageCalculator().search(\n", + " type=\"speed\",\n", + " vessel_status=\"vessel_status_laden_known\",\n", + " origin=ras_tanura,\n", + " destination=rotterdam,\n", + " vessel_class=\"oil_vlcc\",\n", + " ETD=\"2024-03-01T00:00:00.000Z\",\n", + " ETA=\"2024-04-01T00:00:00.000Z\",\n", + ")\n", + "speed_result.to_df()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-7", + "metadata": {}, + "outputs": [], + "source": [ + "# Use lat/long coordinates as origin and avoid the Suez Canal\n", + "latlong_result = VoyageCalculator().search(\n", + " type=\"ETA\",\n", + " vessel_status=\"vessel_status_laden_known\",\n", + " origin={\"lat\": 26.6, \"long\": 50.1},\n", + " destination=rotterdam,\n", + " vessel_class=\"oil_vlcc\",\n", + " ETD=\"2024-03-01T00:00:00.000Z\",\n", + " speed=12,\n", + " avoid_zone=[\"Suez Canal\"],\n", + ")\n", + "latlong_result.to_df()" + ] + }, + { + "cell_type": "markdown", + "id": "cell-8", + "metadata": {}, + "source": [ + "That's it! You've successfully used the Voyage Calculator. Check out https://vortechsa.github.io/python-sdk/ for more examples" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.8.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/endpoints/test_voyage_calculator.py b/tests/endpoints/test_voyage_calculator.py new file mode 100644 index 00000000..a405adaa --- /dev/null +++ b/tests/endpoints/test_voyage_calculator.py @@ -0,0 +1,132 @@ +from tests.testcases import TestCaseUsingRealAPI +from vortexasdk import VoyageCalculator + +ras_tanura = "006bca77c1390ad4daec7e1bff40e6560583be7e4c2caab7cc641db1ae69dd9b" +rotterdam = "68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e" + + +class TestVoyageCalculator(TestCaseUsingRealAPI): + def test_calculate_eta(self): + result = VoyageCalculator().search( + type="ETA", + vessel_status="vessel_status_laden_known", + origin=ras_tanura, + destination=rotterdam, + vessel_class="oil_vlcc", + ETD="2024-03-01T00:00:00.000Z", + speed=12, + ) + + result_list = result.to_list() + assert len(result_list) == 1 + assert "ETA" in result_list[0] + assert "ETD" in result_list[0] + assert "speed" in result_list[0] + assert "duration" in result_list[0] + + def test_calculate_etd(self): + result = VoyageCalculator().search( + type="ETD", + vessel_status="vessel_status_ballast", + origin=ras_tanura, + destination=rotterdam, + vessel_class="oil_vlcc", + ETA="2024-04-01T00:00:00.000Z", + speed=12, + ) + + result_list = result.to_list() + assert len(result_list) == 1 + assert "ETD" in result_list[0] + + def test_calculate_speed(self): + result = VoyageCalculator().search( + type="speed", + vessel_status="vessel_status_laden_known", + origin=ras_tanura, + destination=rotterdam, + vessel_class="oil_vlcc", + ETD="2024-03-01T00:00:00.000Z", + ETA="2024-04-01T00:00:00.000Z", + ) + + result_list = result.to_list() + assert len(result_list) == 1 + assert "speed" in result_list[0] + assert result_list[0]["speed"] > 0 + + def test_calculate_with_latlong_origin(self): + result = VoyageCalculator().search( + type="ETA", + vessel_status="vessel_status_laden_known", + origin={"lat": 26.6, "long": 50.1}, + destination=rotterdam, + vessel_class="oil_vlcc", + ETD="2024-03-01T00:00:00.000Z", + speed=12, + ) + + result_list = result.to_list() + assert len(result_list) == 1 + + def test_calculate_with_avoid_zone(self): + result = VoyageCalculator().search( + type="ETA", + vessel_status="vessel_status_laden_known", + origin=ras_tanura, + destination=rotterdam, + vessel_class="oil_vlcc", + ETD="2024-03-01T00:00:00.000Z", + speed=12, + avoid_zone=["Suez Canal"], + ) + + result_list = result.to_list() + assert len(result_list) == 1 + + def test_to_df(self): + result = VoyageCalculator().search( + type="ETA", + vessel_status="vessel_status_laden_known", + origin=ras_tanura, + destination=rotterdam, + vessel_class="oil_vlcc", + ETD="2024-03-01T00:00:00.000Z", + speed=12, + ) + + df = result.to_df() + assert len(df) == 1 + assert "ETA" in df.columns + assert "ETD" in df.columns + assert "speed" in df.columns + assert "duration" in df.columns + + def test_to_df_with_columns(self): + result = VoyageCalculator().search( + type="ETA", + vessel_status="vessel_status_laden_known", + origin=ras_tanura, + destination=rotterdam, + vessel_class="oil_vlcc", + ETD="2024-03-01T00:00:00.000Z", + speed=12, + ) + + df = result.to_df(columns=["ETA", "speed"]) + assert len(df.columns) == 2 + + def test_calculate_with_delay_factor(self): + result = VoyageCalculator().search( + type="ETA", + vessel_status="vessel_status_laden_known", + origin=ras_tanura, + destination=rotterdam, + vessel_class="oil_vlcc", + ETD="2024-03-01T00:00:00.000Z", + speed=12, + voyage_delay_factor=0.2, + ) + + result_list = result.to_list() + assert len(result_list) == 1 diff --git a/vortexasdk/__init__.py b/vortexasdk/__init__.py index 36805d01..b6d80652 100644 --- a/vortexasdk/__init__.py +++ b/vortexasdk/__init__.py @@ -37,6 +37,10 @@ VoyagesCongestionBreakdown, VoyagesTopHits, VoyagesSearchEnriched, + VoyageCalculator, + VoyageCalculatorType, + VoyageCalculatorVesselStatus, + VoyageCalculatorAvoidZone, VesselSummary, VesselPositions, Refineries, @@ -102,6 +106,10 @@ "VoyagesCongestionBreakdown", "VoyagesTopHits", "VoyagesSearchEnriched", + "VoyageCalculator", + "VoyageCalculatorType", + "VoyageCalculatorVesselStatus", + "VoyageCalculatorAvoidZone", "VesselSummary", "VesselPositions", "Refineries", diff --git a/vortexasdk/endpoints/__init__.py b/vortexasdk/endpoints/__init__.py index be202027..89f47e63 100644 --- a/vortexasdk/endpoints/__init__.py +++ b/vortexasdk/endpoints/__init__.py @@ -63,6 +63,12 @@ ) from vortexasdk.endpoints.voyages_top_hits import VoyagesTopHits from vortexasdk.endpoints.voyages_search_enriched import VoyagesSearchEnriched +from vortexasdk.endpoints.voyage_calculator import ( + VoyageCalculator, + VoyageCalculatorType, + VoyageCalculatorVesselStatus, + VoyageCalculatorAvoidZone, +) from vortexasdk.endpoints.fixtures import Fixtures from vortexasdk.endpoints.vessel_summary import VesselSummary from vortexasdk.endpoints.vessel_positions import VesselPositions @@ -154,6 +160,11 @@ "AnywhereFreightPricingTopPortsOrigin", "AnywhereFreightPricingVesselClassesDetails", "AnywhereFreightPricingForecastExplanation", + "VoyageCalculator", + # Voyage Calculator types + "VoyageCalculatorType", + "VoyageCalculatorVesselStatus", + "VoyageCalculatorAvoidZone", # AFP types for user type annotations "AfpAvoidZone", "AfpExplanationFrequency", diff --git a/vortexasdk/endpoints/endpoints.py b/vortexasdk/endpoints/endpoints.py index 896c8173..3cdd37eb 100644 --- a/vortexasdk/endpoints/endpoints.py +++ b/vortexasdk/endpoints/endpoints.py @@ -69,6 +69,8 @@ FIXTURES = "/v5/search/fixtures" +VOYAGE_CALCULATOR = "/v5/voyages/voyage-calculator" + CANAL_TRANSIT = "/v5/canal-transit" CANAL_TRANSIT_SEARCH = "/v5/canal-transit/search" CANAL_TRANSIT_TIME_SERIES = "/v5/canal-transit/time-series" diff --git a/vortexasdk/endpoints/voyage_calculator.py b/vortexasdk/endpoints/voyage_calculator.py new file mode 100644 index 00000000..5fefb383 --- /dev/null +++ b/vortexasdk/endpoints/voyage_calculator.py @@ -0,0 +1,149 @@ +""" +Try me out in your browser: + +[![Binder](https://img.shields.io/badge/try%20me%20out-launch%20notebook-579ACA.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAABZCAMAAABi1XidAAAB8lBMVEX///9XmsrmZYH1olJXmsr1olJXmsrmZYH1olJXmsr1olJXmsrmZYH1olL1olJXmsr1olJXmsrmZYH1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olJXmsrmZYH1olL1olL0nFf1olJXmsrmZYH1olJXmsq8dZb1olJXmsrmZYH1olJXmspXmspXmsr1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olLeaIVXmsrmZYH1olL1olL1olJXmsrmZYH1olLna31Xmsr1olJXmsr1olJXmsrmZYH1olLqoVr1olJXmsr1olJXmsrmZYH1olL1olKkfaPobXvviGabgadXmsqThKuofKHmZ4Dobnr1olJXmsr1olJXmspXmsr1olJXmsrfZ4TuhWn1olL1olJXmsqBi7X1olJXmspZmslbmMhbmsdemsVfl8ZgmsNim8Jpk8F0m7R4m7F5nLB6jbh7jbiDirOEibOGnKaMhq+PnaCVg6qWg6qegKaff6WhnpKofKGtnomxeZy3noG6dZi+n3vCcpPDcpPGn3bLb4/Mb47UbIrVa4rYoGjdaIbeaIXhoWHmZYHobXvpcHjqdHXreHLroVrsfG/uhGnuh2bwj2Hxk17yl1vzmljzm1j0nlX1olL3AJXWAAAAbXRSTlMAEBAQHx8gICAuLjAwMDw9PUBAQEpQUFBXV1hgYGBkcHBwcXl8gICAgoiIkJCQlJicnJ2goKCmqK+wsLC4usDAwMjP0NDQ1NbW3Nzg4ODi5+3v8PDw8/T09PX29vb39/f5+fr7+/z8/Pz9/v7+zczCxgAABC5JREFUeAHN1ul3k0UUBvCb1CTVpmpaitAGSLSpSuKCLWpbTKNJFGlcSMAFF63iUmRccNG6gLbuxkXU66JAUef/9LSpmXnyLr3T5AO/rzl5zj137p136BISy44fKJXuGN/d19PUfYeO67Znqtf2KH33Id1psXoFdW30sPZ1sMvs2D060AHqws4FHeJojLZqnw53cmfvg+XR8mC0OEjuxrXEkX5ydeVJLVIlV0e10PXk5k7dYeHu7Cj1j+49uKg7uLU61tGLw1lq27ugQYlclHC4bgv7VQ+TAyj5Zc/UjsPvs1sd5cWryWObtvWT2EPa4rtnWW3JkpjggEpbOsPr7F7EyNewtpBIslA7p43HCsnwooXTEc3UmPmCNn5lrqTJxy6nRmcavGZVt/3Da2pD5NHvsOHJCrdc1G2r3DITpU7yic7w/7Rxnjc0kt5GC4djiv2Sz3Fb2iEZg41/ddsFDoyuYrIkmFehz0HR2thPgQqMyQYb2OtB0WxsZ3BeG3+wpRb1vzl2UYBog8FfGhttFKjtAclnZYrRo9ryG9uG/FZQU4AEg8ZE9LjGMzTmqKXPLnlWVnIlQQTvxJf8ip7VgjZjyVPrjw1te5otM7RmP7xm+sK2Gv9I8Gi++BRbEkR9EBw8zRUcKxwp73xkaLiqQb+kGduJTNHG72zcW9LoJgqQxpP3/Tj//c3yB0tqzaml05/+orHLksVO+95kX7/7qgJvnjlrfr2Ggsyx0eoy9uPzN5SPd86aXggOsEKW2Prz7du3VID3/tzs/sSRs2w7ovVHKtjrX2pd7ZMlTxAYfBAL9jiDwfLkq55Tm7ifhMlTGPyCAs7RFRhn47JnlcB9RM5T97ASuZXIcVNuUDIndpDbdsfrqsOppeXl5Y+XVKdjFCTh+zGaVuj0d9zy05PPK3QzBamxdwtTCrzyg/2Rvf2EstUjordGwa/kx9mSJLr8mLLtCW8HHGJc2R5hS219IiF6PnTusOqcMl57gm0Z8kanKMAQg0qSyuZfn7zItsbGyO9QlnxY0eCuD1XL2ys/MsrQhltE7Ug0uFOzufJFE2PxBo/YAx8XPPdDwWN0MrDRYIZF0mSMKCNHgaIVFoBbNoLJ7tEQDKxGF0kcLQimojCZopv0OkNOyWCCg9XMVAi7ARJzQdM2QUh0gmBozjc3Skg6dSBRqDGYSUOu66Zg+I2fNZs/M3/f/Grl/XnyF1Gw3VKCez0PN5IUfFLqvgUN4C0qNqYs5YhPL+aVZYDE4IpUk57oSFnJm4FyCqqOE0jhY2SMyLFoo56zyo6becOS5UVDdj7Vih0zp+tcMhwRpBeLyqtIjlJKAIZSbI8SGSF3k0pA3mR5tHuwPFoa7N7reoq2bqCsAk1HqCu5uvI1n6JuRXI+S1Mco54YmYTwcn6Aeic+kssXi8XpXC4V3t7/ADuTNKaQJdScAAAAAElFTkSuQmCC)](https://mybinder.org/v2/gh/VorTECHsa/python-sdk/master?filepath=docs%2Fexamples%2Ftry_me_out%2Fvoyage_calculator.ipynb) +""" + +from typing import Any, Dict, List, Optional, Union + +from vortexasdk.endpoints.endpoints import VOYAGE_CALCULATOR +from vortexasdk.endpoints.voyage_calculator_result import ( + VoyageCalculatorResult, +) +from vortexasdk.operations import Search + +from typing_extensions import Literal + +VoyageCalculatorType = Literal["speed", "ETA", "ETD"] +VoyageCalculatorVesselStatus = Literal[ + "vessel_status_ballast", + "vessel_status_laden_known", + "vessel_status_laden_unknown", +] +VoyageCalculatorAvoidZone = Literal["Panama Canal", "Suez Canal"] + +LatLong = Dict[str, float] + + +class VoyageCalculator(Search): + """ + Voyage Calculator endpoint. + + Calculates voyage routes, ETAs, ETDs, or speeds between an origin and destination + using Vortexa's pathfinder routing engine. The calculator accounts for vessel class, + laden/ballast status, canal avoidance zones, and optional waypoints. + """ + + def __init__(self) -> None: + Search.__init__(self, VOYAGE_CALCULATOR) + + def search( + self, + type: VoyageCalculatorType, + vessel_status: VoyageCalculatorVesselStatus, + origin: Union[str, LatLong], + destination: Union[str, LatLong], + vessel_id: Optional[str] = None, + vessel_class: Optional[str] = None, + waypoints: Optional[List[str]] = None, + ETA: Optional[str] = None, + ETD: Optional[str] = None, + speed: Optional[float] = None, + avoid_zone: Optional[List[VoyageCalculatorAvoidZone]] = None, + voyage_delay_factor: Optional[float] = None, + ) -> "VoyageCalculatorResult": + """ + Calculate a voyage route between an origin and destination. + + # Arguments + type: The type of calculation to perform. One of: + - `'speed'`: Calculate the speed required given an ETD and ETA. + - `'ETA'`: Calculate the ETA given an ETD and speed. + - `'ETD'`: Calculate the ETD given an ETA and speed. + + vessel_status: Whether the vessel is laden or ballast. One of: + `'vessel_status_ballast'`, `'vessel_status_laden_known'`, `'vessel_status_laden_unknown'`. + + origin: The origin of the voyage. Can be either: + - A string ID (vessel ID for current position, or geography ID for centroid). + - A dict with `lat` and `long` keys, e.g. `{"lat": 51.9, "long": 4.5}`. + + destination: The destination of the voyage. Can be either: + - A geography ID string. + - A dict with `lat` and `long` keys, e.g. `{"lat": 29.9, "long": 32.5}`. + + vessel_id: A vessel identifier (IMO, MMSI, vessel name, or Vortexa ID). + Used to determine the vessel's current position (when origin is a vessel ID) + and deadweight tonnage for routing. + + vessel_class: Vessel class used to determine DWT when `vessel_id` is not provided. + Examples: `'oil_vlcc'`, `'oil_suezmax_lr3'`, `'oil_aframax_lr2'`, `'lng_conventional_lng'`. + + waypoints: A list of geography IDs representing intermediate waypoints. + + ETA: Estimated time of arrival as an ISO 8601 date string (e.g. `'2024-03-15T00:00:00.000Z'`). + Required when `type` is `'speed'` or `'ETD'`. + + ETD: Estimated time of departure as an ISO 8601 date string (e.g. `'2024-03-01T00:00:00.000Z'`). + Required when `type` is `'speed'` or `'ETA'`. + + speed: Speed in knots. Required when `type` is `'ETA'` or `'ETD'`. + + avoid_zone: A list of zones to avoid in routing. Options: `'Panama Canal'`, `'Suez Canal'`. + + voyage_delay_factor: A factor between 0 and 1 to simulate increased voyage duration. + For example, 0.2 means 120% of the original duration. + + # Returns + `VoyageCalculatorResult` + + # Example + + _Calculate ETA for a VLCC travelling from Ras Tanura to Rotterdam at 12 knots._ + + ```python + >>> from vortexasdk import VoyageCalculator + >>> ras_tanura = "006bca77c1390ad4daec7e1bff40e6560583be7e4c2caab7cc641db1ae69dd9b" + >>> rotterdam = "68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e" + >>> result = VoyageCalculator().search( + ... type="ETA", + ... vessel_status="vessel_status_laden_known", + ... origin=ras_tanura, + ... destination=rotterdam, + ... vessel_class="oil_vlcc", + ... ETD="2024-03-01T00:00:00.000Z", + ... speed=12, + ... ) + >>> df = result.to_df() + + ``` + + Returns a DataFrame with columns: + + | | ETA | ETD | duration | speed | + |---:|:-------------------------|:-------------------------|-----------:|--------:| + | 0 | 2024-03-25T14:30:00.000Z | 2024-03-01T00:00:00.000Z | 590.5 | 12 | + + """ + api_params: Dict[str, Any] = { + "type": type, + "vessel_status": vessel_status, + "origin": origin, + "destination": destination, + "vessel_id": vessel_id, + "vessel_class": vessel_class, + "waypoints": waypoints, + "ETA": ETA, + "ETD": ETD, + "speed": speed, + "avoid_zone": avoid_zone, + "voyage_delay_factor": voyage_delay_factor, + } + + response = super().search_with_client( + response_type="breakdown", **api_params + ) + + return VoyageCalculatorResult( + records=response["data"], reference=response.get("reference", {}) + ) diff --git a/vortexasdk/endpoints/voyage_calculator_result.py b/vortexasdk/endpoints/voyage_calculator_result.py new file mode 100644 index 00000000..ed6e1ae6 --- /dev/null +++ b/vortexasdk/endpoints/voyage_calculator_result.py @@ -0,0 +1,44 @@ +from typing import List, Optional, Union + +import pandas as pd +from typing_extensions import Literal + +from vortexasdk.api.search_result import Result + + +class VoyageCalculatorResult(Result): + """ + Container class holding results returned from the voyage calculator endpoint. + + This class has `to_list()` and `to_df()` methods for representing results. + """ + + def to_list(self) -> List[dict]: + """Represent voyage calculations as a list of dictionaries.""" + return super().to_list() + + def to_df( + self, columns: Optional[Union[List[str], Literal["all"]]] = "all" + ) -> pd.DataFrame: + """ + Represent voyage calculations as a `pd.DataFrame`. + + # Arguments + columns: Output columns present in the `pd.DataFrame`. + Enter `columns='all'` to return all available columns. + Enter a list of column names to return only those columns. + + # Returns + `pd.DataFrame` with one row per calculated voyage. + + """ + if not self.records: + return pd.DataFrame() + + df = pd.json_normalize(self.records) + + if columns is None or columns == "all": + return df + + available_columns = [col for col in columns if col in df.columns] + return df[available_columns] From 00b272bf9f9ca063d518bb156fd93c29e00b27ff Mon Sep 17 00:00:00 2001 From: Alex Spedding Date: Wed, 26 Aug 2026 16:39:55 +0100 Subject: [PATCH 2/6] fix: use correct Ras Tanura geography ID in tests and docs Co-Authored-By: Claude Opus 4.6 --- .../try_me_out/voyage_calculator.ipynb | 19 ++----------------- tests/endpoints/test_voyage_calculator.py | 2 +- vortexasdk/endpoints/voyage_calculator.py | 2 +- 3 files changed, 4 insertions(+), 19 deletions(-) diff --git a/docs/examples/try_me_out/voyage_calculator.ipynb b/docs/examples/try_me_out/voyage_calculator.ipynb index b23a9568..029b3fc7 100644 --- a/docs/examples/try_me_out/voyage_calculator.ipynb +++ b/docs/examples/try_me_out/voyage_calculator.ipynb @@ -40,22 +40,7 @@ "id": "cell-4", "metadata": {}, "outputs": [], - "source": [ - "# Calculate ETA for a VLCC travelling from Ras Tanura to Rotterdam at 12 knots\n", - "ras_tanura = \"006bca77c1390ad4daec7e1bff40e6560583be7e4c2caab7cc641db1ae69dd9b\"\n", - "rotterdam = \"68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e\"\n", - "\n", - "result = VoyageCalculator().search(\n", - " type=\"ETA\",\n", - " vessel_status=\"vessel_status_laden_known\",\n", - " origin=ras_tanura,\n", - " destination=rotterdam,\n", - " vessel_class=\"oil_vlcc\",\n", - " ETD=\"2024-03-01T00:00:00.000Z\",\n", - " speed=12,\n", - ")\n", - "df = result.to_df()" - ] + "source": "# Calculate ETA for a VLCC travelling from Ras Tanura to Rotterdam at 12 knots\nras_tanura = \"539db1548407fd97024391d01a6a2be239b1100f070a137d54a79907d03db6c8\"\nrotterdam = \"68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e\"\n\nresult = VoyageCalculator().search(\n type=\"ETA\",\n vessel_status=\"vessel_status_laden_known\",\n origin=ras_tanura,\n destination=rotterdam,\n vessel_class=\"oil_vlcc\",\n ETD=\"2024-03-01T00:00:00.000Z\",\n speed=12,\n)\ndf = result.to_df()" }, { "cell_type": "code", @@ -130,4 +115,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/tests/endpoints/test_voyage_calculator.py b/tests/endpoints/test_voyage_calculator.py index a405adaa..96765e91 100644 --- a/tests/endpoints/test_voyage_calculator.py +++ b/tests/endpoints/test_voyage_calculator.py @@ -1,7 +1,7 @@ from tests.testcases import TestCaseUsingRealAPI from vortexasdk import VoyageCalculator -ras_tanura = "006bca77c1390ad4daec7e1bff40e6560583be7e4c2caab7cc641db1ae69dd9b" +ras_tanura = "539db1548407fd97024391d01a6a2be239b1100f070a137d54a79907d03db6c8" rotterdam = "68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e" diff --git a/vortexasdk/endpoints/voyage_calculator.py b/vortexasdk/endpoints/voyage_calculator.py index 5fefb383..c6a39d88 100644 --- a/vortexasdk/endpoints/voyage_calculator.py +++ b/vortexasdk/endpoints/voyage_calculator.py @@ -103,7 +103,7 @@ def search( ```python >>> from vortexasdk import VoyageCalculator - >>> ras_tanura = "006bca77c1390ad4daec7e1bff40e6560583be7e4c2caab7cc641db1ae69dd9b" + >>> ras_tanura = "539db1548407fd97024391d01a6a2be239b1100f070a137d54a79907d03db6c8" >>> rotterdam = "68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e" >>> result = VoyageCalculator().search( ... type="ETA", From c59c430382cb4e62598ec34b80bd54df4ac36387 Mon Sep 17 00:00:00 2001 From: Alex Spedding Date: Wed, 26 Aug 2026 16:55:26 +0100 Subject: [PATCH 3/6] chore: remove notebook example, fix endpoint numbering in docs Co-Authored-By: Claude Opus 4.6 --- docs/endpoints/about-endpoints.md | 34 ++--- .../try_me_out/voyage_calculator.ipynb | 118 ------------------ vortexasdk/endpoints/voyage_calculator.py | 6 - 3 files changed, 17 insertions(+), 141 deletions(-) delete mode 100644 docs/examples/try_me_out/voyage_calculator.ipynb diff --git a/docs/endpoints/about-endpoints.md b/docs/endpoints/about-endpoints.md index f5f5b635..ad78245f 100644 --- a/docs/endpoints/about-endpoints.md +++ b/docs/endpoints/about-endpoints.md @@ -5,23 +5,23 @@ The endpoints module allows you to query Vortexa's data. The VortexaSDK currently contains the following endpoints: 1. Cargo Movements -1. Voyages -1. Charterers -1. Geographies -1. Products -1. Vessels -1. Cargo Time Series -1. EIA Forecasts -1. Tonne-miles -1. Vessel Availability -1. Crude Onshore Inventories -1. Freight Pricing -1. Vessel Summary -1. Vessel Positions -1. Canal transit -1. Canal transit Time Series -1. Refineries -1. Voyage Calculator +2. Voyages +3. Charterers +4. Geographies +5. Products +6. Vessels +7. Cargo Time Series +8. EIA Forecasts +9. Tonne-miles +10. Vessel Availability +11. Crude Onshore Inventories +12. Freight Pricing +13. Vessel Summary +14. Vessel Positions +15. Canal transit +16. Canal transit Time Series +17. Refineries +18. Voyage Calculator Each endpoint offers either one, or both, of two different functionalities: diff --git a/docs/examples/try_me_out/voyage_calculator.ipynb b/docs/examples/try_me_out/voyage_calculator.ipynb deleted file mode 100644 index 029b3fc7..00000000 --- a/docs/examples/try_me_out/voyage_calculator.ipynb +++ /dev/null @@ -1,118 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "cell-0", - "metadata": {}, - "source": [ - "### Try out the VortexaSDK" - ] - }, - { - "cell_type": "markdown", - "id": "cell-1", - "metadata": {}, - "source": [ - "First let's import our requirements" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cell-2", - "metadata": {}, - "outputs": [], - "source": [ - "from vortexasdk import VoyageCalculator" - ] - }, - { - "cell_type": "markdown", - "id": "cell-3", - "metadata": {}, - "source": [ - "You'll need to enter your Vortexa API key when prompted." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cell-4", - "metadata": {}, - "outputs": [], - "source": "# Calculate ETA for a VLCC travelling from Ras Tanura to Rotterdam at 12 knots\nras_tanura = \"539db1548407fd97024391d01a6a2be239b1100f070a137d54a79907d03db6c8\"\nrotterdam = \"68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e\"\n\nresult = VoyageCalculator().search(\n type=\"ETA\",\n vessel_status=\"vessel_status_laden_known\",\n origin=ras_tanura,\n destination=rotterdam,\n vessel_class=\"oil_vlcc\",\n ETD=\"2024-03-01T00:00:00.000Z\",\n speed=12,\n)\ndf = result.to_df()" - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cell-5", - "metadata": {}, - "outputs": [], - "source": [ - "df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cell-6", - "metadata": {}, - "outputs": [], - "source": [ - "# Calculate speed required to arrive by a specific date\n", - "speed_result = VoyageCalculator().search(\n", - " type=\"speed\",\n", - " vessel_status=\"vessel_status_laden_known\",\n", - " origin=ras_tanura,\n", - " destination=rotterdam,\n", - " vessel_class=\"oil_vlcc\",\n", - " ETD=\"2024-03-01T00:00:00.000Z\",\n", - " ETA=\"2024-04-01T00:00:00.000Z\",\n", - ")\n", - "speed_result.to_df()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cell-7", - "metadata": {}, - "outputs": [], - "source": [ - "# Use lat/long coordinates as origin and avoid the Suez Canal\n", - "latlong_result = VoyageCalculator().search(\n", - " type=\"ETA\",\n", - " vessel_status=\"vessel_status_laden_known\",\n", - " origin={\"lat\": 26.6, \"long\": 50.1},\n", - " destination=rotterdam,\n", - " vessel_class=\"oil_vlcc\",\n", - " ETD=\"2024-03-01T00:00:00.000Z\",\n", - " speed=12,\n", - " avoid_zone=[\"Suez Canal\"],\n", - ")\n", - "latlong_result.to_df()" - ] - }, - { - "cell_type": "markdown", - "id": "cell-8", - "metadata": {}, - "source": [ - "That's it! You've successfully used the Voyage Calculator. Check out https://vortechsa.github.io/python-sdk/ for more examples" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3.8.0" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file diff --git a/vortexasdk/endpoints/voyage_calculator.py b/vortexasdk/endpoints/voyage_calculator.py index c6a39d88..ce44a572 100644 --- a/vortexasdk/endpoints/voyage_calculator.py +++ b/vortexasdk/endpoints/voyage_calculator.py @@ -1,9 +1,3 @@ -""" -Try me out in your browser: - -[![Binder](https://img.shields.io/badge/try%20me%20out-launch%20notebook-579ACA.svg?logo=data:image/png;base64,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)](https://mybinder.org/v2/gh/VorTECHsa/python-sdk/master?filepath=docs%2Fexamples%2Ftry_me_out%2Fvoyage_calculator.ipynb) -""" - from typing import Any, Dict, List, Optional, Union from vortexasdk.endpoints.endpoints import VOYAGE_CALCULATOR From 7b9aec8e09b9c044d66203b65c150f84b7a82ad4 Mon Sep 17 00:00:00 2001 From: Alex Spedding Date: Thu, 27 Aug 2026 11:48:01 +0100 Subject: [PATCH 4/6] chore: amend docs --- vortexasdk/endpoints/voyage_calculator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vortexasdk/endpoints/voyage_calculator.py b/vortexasdk/endpoints/voyage_calculator.py index ce44a572..ee0ddd87 100644 --- a/vortexasdk/endpoints/voyage_calculator.py +++ b/vortexasdk/endpoints/voyage_calculator.py @@ -23,9 +23,9 @@ class VoyageCalculator(Search): """ Voyage Calculator endpoint. - Calculates voyage routes, ETAs, ETDs, or speeds between an origin and destination - using Vortexa's pathfinder routing engine. The calculator accounts for vessel class, - laden/ballast status, canal avoidance zones, and optional waypoints. + Calculates voyage routes, ETAs, ETDs, or speeds between an origin and destination. + The calculator accounts for vessel class, laden/ballast status, canal avoidance + zones, and optional waypoints. """ def __init__(self) -> None: From 4ffcff12bcb4c7216abe143823d7b05127c42cf6 Mon Sep 17 00:00:00 2001 From: Alex Spedding Date: Thu, 27 Aug 2026 12:05:10 +0100 Subject: [PATCH 5/6] chore: bump version to 1.0.31 Co-Authored-By: Claude Opus 4.6 --- vortexasdk/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vortexasdk/version.py b/vortexasdk/version.py index 8873ad42..44e67809 100644 --- a/vortexasdk/version.py +++ b/vortexasdk/version.py @@ -1 +1 @@ -__version__ = "1.0.30a1" +__version__ = "1.0.31" From fa7a1dac01270f89b8dbedce79d45a5bbf07799f Mon Sep 17 00:00:00 2001 From: Alex Spedding Date: Thu, 27 Aug 2026 13:29:11 +0100 Subject: [PATCH 6/6] fix: remove trailing whitespace in voyage_calculator.py Co-Authored-By: Claude Opus 4.6 --- vortexasdk/endpoints/voyage_calculator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vortexasdk/endpoints/voyage_calculator.py b/vortexasdk/endpoints/voyage_calculator.py index ee0ddd87..c30c9c53 100644 --- a/vortexasdk/endpoints/voyage_calculator.py +++ b/vortexasdk/endpoints/voyage_calculator.py @@ -23,8 +23,8 @@ class VoyageCalculator(Search): """ Voyage Calculator endpoint. - Calculates voyage routes, ETAs, ETDs, or speeds between an origin and destination. - The calculator accounts for vessel class, laden/ballast status, canal avoidance + Calculates voyage routes, ETAs, ETDs, or speeds between an origin and destination. + The calculator accounts for vessel class, laden/ballast status, canal avoidance zones, and optional waypoints. """