diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index bcca56f1..f7bcaade 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -46,10 +46,10 @@ pip install -e '.[tests]'
To run the live tests, you'll need to have the `VORTEXA_API_KEY` environment variable set - `export VORTEXA_API_KEY=xyz`
-run tests
+run all tests
```bash
-python setup.py test
+pytest
```
If you're just looking to run tests in a single module (`test_vessels` in this case), you can do like this:
diff --git a/docs/autogen.py b/docs/autogen.py
index adca3723..93cecc46 100644
--- a/docs/autogen.py
+++ b/docs/autogen.py
@@ -53,6 +53,20 @@ def copy_examples(examples_dir: str, destination_dir: str) -> None:
f_out.write("```")
+def copy_css(source_dir: str, destination_dir: str) -> None:
+ """Copy CSS files to the documentation build directory."""
+ pathlib.Path(destination_dir).mkdir(parents=True, exist_ok=True)
+ for file in os.listdir(source_dir):
+ if file.endswith(".css"):
+ source_path = os.path.join(source_dir, file)
+ dest_path = os.path.join(destination_dir, file)
+ with open(source_path, "r", encoding="utf-8") as f_in:
+ content = f_in.read()
+ with open(dest_path, "w", encoding="utf-8") as f_out:
+ f_out.write(content)
+
+
if __name__ == "__main__":
print(os.getcwd())
copy_examples("./docs/examples", "./_build/pydocmd/examples")
+ copy_css("./docs/css", "./_build/pydocmd/css")
diff --git a/docs/css/custom.css b/docs/css/custom.css
new file mode 100644
index 00000000..5d01ba70
--- /dev/null
+++ b/docs/css/custom.css
@@ -0,0 +1,5 @@
+/* Fix long module names overflowing */
+h1, h2, h3, h4, h5, h6 {
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+}
diff --git a/docs/examples/try_me_out/anywhere_freight_pricing_get_price_details.ipynb b/docs/examples/try_me_out/anywhere_freight_pricing_get_price_details.ipynb
new file mode 100644
index 00000000..8a6c75dd
--- /dev/null
+++ b/docs/examples/try_me_out/anywhere_freight_pricing_get_price_details.ipynb
@@ -0,0 +1,184 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Try out the VortexaSDK"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First let's import our requirements"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from vortexasdk import AnywhereFreightPricingGetPriceDetails\n",
+ "from datetime import datetime"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "You'll need to enter your Vortexa API key when prompted."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Get price details for an Aframax LR2 crude route from Houston to Rotterdam\n",
+ "result = AnywhereFreightPricingGetPriceDetails().search(\n",
+ " time_min=datetime(2024, 1, 1),\n",
+ " time_max=datetime(2024, 12, 31),\n",
+ " origin_port=\"7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030ec6b82f944a73d4da2f\",\n",
+ " destination_port=\"68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e\",\n",
+ " vessel_class=\"oil_aframax_lr2\",\n",
+ " product=\"crude\",\n",
+ " unit=\"usd_per_tonne\",\n",
+ ")\n",
+ "df = result.to_df()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " vessel_class | \n",
+ " max_dwt | \n",
+ " suggested_tonnage | \n",
+ " suggested_tonnage_overridden | \n",
+ " avoid_zone | \n",
+ " via_waypoint | \n",
+ " rates | \n",
+ " lumpsums | \n",
+ " confidences | \n",
+ " product | \n",
+ " origin_port.id | \n",
+ " origin_port.lat | \n",
+ " origin_port.lon | \n",
+ " destination_port.id | \n",
+ " destination_port.lat | \n",
+ " destination_port.lon | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " oil_aframax_lr2 | \n",
+ " 119999.0 | \n",
+ " 85000.0 | \n",
+ " False | \n",
+ " None | \n",
+ " None | \n",
+ " [{'date': '2024-01-01', 'breakdown': [{'type':... | \n",
+ " [{'date': '2024-01-01', 'breakdown': [{'type':... | \n",
+ " [{'date': '2024-01-01', 'value': 2}, {'date': ... | \n",
+ " crude | \n",
+ " 7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030e... | \n",
+ " 29.71926 | \n",
+ " -95.14733 | \n",
+ " 68faf65af1345067f11dc6723b8da32f00e304a6f33c00... | \n",
+ " 51.90924 | \n",
+ " 4.27941 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " vessel_class max_dwt suggested_tonnage suggested_tonnage_overridden \\\n",
+ "0 oil_aframax_lr2 119999.0 85000.0 False \n",
+ "\n",
+ " avoid_zone via_waypoint rates \\\n",
+ "0 None None [{'date': '2024-01-01', 'breakdown': [{'type':... \n",
+ "\n",
+ " lumpsums \\\n",
+ "0 [{'date': '2024-01-01', 'breakdown': [{'type':... \n",
+ "\n",
+ " confidences product \\\n",
+ "0 [{'date': '2024-01-01', 'value': 2}, {'date': ... crude \n",
+ "\n",
+ " origin_port.id origin_port.lat \\\n",
+ "0 7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030e... 29.71926 \n",
+ "\n",
+ " origin_port.lon destination_port.id \\\n",
+ "0 -95.14733 68faf65af1345067f11dc6723b8da32f00e304a6f33c00... \n",
+ "\n",
+ " destination_port.lat destination_port.lon \n",
+ "0 51.90924 4.27941 "
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df.head()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "That's it! You've successfully loaded data using the Vortexa SDK. Check out https://vortechsa.github.io/python-sdk/ for more examples"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.13.13"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/docs/examples/try_me_out/anywhere_freight_pricing_latest_update_timestamp.ipynb b/docs/examples/try_me_out/anywhere_freight_pricing_latest_update_timestamp.ipynb
new file mode 100644
index 00000000..55847fca
--- /dev/null
+++ b/docs/examples/try_me_out/anywhere_freight_pricing_latest_update_timestamp.ipynb
@@ -0,0 +1,72 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Try out the VortexaSDK"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First let's import our requirements"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from vortexasdk import AnywhereFreightPricingLatestUpdateTimestamp"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "You'll need to enter your Vortexa API key when prompted."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "result = AnywhereFreightPricingLatestUpdateTimestamp().search()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "result"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "That's it! You've successfully loaded data using the Vortexa SDK. 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.11.0"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/docs/examples/try_me_out/anywhere_freight_pricing_post_price_details.ipynb b/docs/examples/try_me_out/anywhere_freight_pricing_post_price_details.ipynb
new file mode 100644
index 00000000..25699129
--- /dev/null
+++ b/docs/examples/try_me_out/anywhere_freight_pricing_post_price_details.ipynb
@@ -0,0 +1,230 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Try out the VortexaSDK"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First let's import our requirements"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from vortexasdk import AnywhereFreightPricingPostPriceDetails\n",
+ "from datetime import datetime"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "You'll need to enter your Vortexa API key when prompted."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Get price details for multiple routes\n",
+ "routes = [\n",
+ " {\n",
+ " \"origin_port\": \"7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030ec6b82f944a73d4da2f\",\n",
+ " \"destination_port\": \"68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e\",\n",
+ " \"product\": \"crude\",\n",
+ " \"vessel_class\": \"oil_aframax_lr2\",\n",
+ " },\n",
+ " {\n",
+ " \"origin_port\": \"68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e\",\n",
+ " \"destination_port\": \"ea4921c8ad4fddb5fe3e7a4f834c1aa5863e43283c73da5f02d93bbc5dba72eb\",\n",
+ " \"product\": \"clean\",\n",
+ " \"vessel_class\": \"oil_handymax_mr2\",\n",
+ " }\n",
+ "]\n",
+ "result = AnywhereFreightPricingPostPriceDetails().search(\n",
+ " routes=routes,\n",
+ " time_min=datetime(2024, 1, 1),\n",
+ " time_max=datetime(2024, 1, 31),\n",
+ " unit=\"usd_per_tonne\",\n",
+ ")\n",
+ "df = result.to_df()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " vessel_class | \n",
+ " max_dwt | \n",
+ " suggested_tonnage | \n",
+ " suggested_tonnage_overridden | \n",
+ " avoid_zone | \n",
+ " via_waypoint | \n",
+ " rates | \n",
+ " lumpsums | \n",
+ " confidences | \n",
+ " product | \n",
+ " origin_port.id | \n",
+ " origin_port.lat | \n",
+ " origin_port.lon | \n",
+ " destination_port.id | \n",
+ " destination_port.lat | \n",
+ " destination_port.lon | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " oil_aframax_lr2 | \n",
+ " 119999.0 | \n",
+ " 85000.0 | \n",
+ " False | \n",
+ " None | \n",
+ " None | \n",
+ " [{'date': '2024-01-01', 'breakdown': [{'type':... | \n",
+ " [{'date': '2024-01-01', 'breakdown': [{'type':... | \n",
+ " [{'date': '2024-01-01', 'value': 2}, {'date': ... | \n",
+ " crude | \n",
+ " 7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030e... | \n",
+ " 29.71926 | \n",
+ " -95.14733 | \n",
+ " 68faf65af1345067f11dc6723b8da32f00e304a6f33c00... | \n",
+ " 51.90924 | \n",
+ " 4.27941 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " oil_handymax_mr2 | \n",
+ " 54999.0 | \n",
+ " 37000.0 | \n",
+ " False | \n",
+ " None | \n",
+ " None | \n",
+ " [{'date': '2024-01-01', 'breakdown': [{'type':... | \n",
+ " [{'date': '2024-01-01', 'breakdown': [{'type':... | \n",
+ " [{'date': '2024-01-01', 'value': 3}, {'date': ... | \n",
+ " clean | \n",
+ " 68faf65af1345067f11dc6723b8da32f00e304a6f33c00... | \n",
+ " 51.90924 | \n",
+ " 4.27941 | \n",
+ " ea4921c8ad4fddb5fe3e7a4f834c1aa5863e43283c73da... | \n",
+ " 40.68878 | \n",
+ " -74.05066 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " vessel_class max_dwt suggested_tonnage \\\n",
+ "0 oil_aframax_lr2 119999.0 85000.0 \n",
+ "1 oil_handymax_mr2 54999.0 37000.0 \n",
+ "\n",
+ " suggested_tonnage_overridden avoid_zone via_waypoint \\\n",
+ "0 False None None \n",
+ "1 False None None \n",
+ "\n",
+ " rates \\\n",
+ "0 [{'date': '2024-01-01', 'breakdown': [{'type':... \n",
+ "1 [{'date': '2024-01-01', 'breakdown': [{'type':... \n",
+ "\n",
+ " lumpsums \\\n",
+ "0 [{'date': '2024-01-01', 'breakdown': [{'type':... \n",
+ "1 [{'date': '2024-01-01', 'breakdown': [{'type':... \n",
+ "\n",
+ " confidences product \\\n",
+ "0 [{'date': '2024-01-01', 'value': 2}, {'date': ... crude \n",
+ "1 [{'date': '2024-01-01', 'value': 3}, {'date': ... clean \n",
+ "\n",
+ " origin_port.id origin_port.lat \\\n",
+ "0 7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030e... 29.71926 \n",
+ "1 68faf65af1345067f11dc6723b8da32f00e304a6f33c00... 51.90924 \n",
+ "\n",
+ " origin_port.lon destination_port.id \\\n",
+ "0 -95.14733 68faf65af1345067f11dc6723b8da32f00e304a6f33c00... \n",
+ "1 4.27941 ea4921c8ad4fddb5fe3e7a4f834c1aa5863e43283c73da... \n",
+ "\n",
+ " destination_port.lat destination_port.lon \n",
+ "0 51.90924 4.27941 \n",
+ "1 40.68878 -74.05066 "
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df.head()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "That's it! You've successfully loaded data using the Vortexa SDK. Check out https://vortechsa.github.io/python-sdk/ for more examples"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.13.13"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/docs/examples/try_me_out/anywhere_freight_pricing_price_timeseries.ipynb b/docs/examples/try_me_out/anywhere_freight_pricing_price_timeseries.ipynb
new file mode 100644
index 00000000..4f6aaa21
--- /dev/null
+++ b/docs/examples/try_me_out/anywhere_freight_pricing_price_timeseries.ipynb
@@ -0,0 +1,97 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Try out the VortexaSDK"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First let's import our requirements"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from vortexasdk import AnywhereFreightPricingPriceTimeseries\n",
+ "from datetime import datetime"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "You'll need to enter your Vortexa API key when prompted."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Get daily pricing for a Handymax MR2 clean route from Rotterdam to New York\n",
+ "routes = [\n",
+ " {\n",
+ " \"origin_port\": \"68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e\",\n",
+ " \"destination_port\": \"ea4921c8ad4fddb5fe3e7a4f834c1aa5863e43283c73da5f02d93bbc5dba72eb\",\n",
+ " \"product\": \"clean\",\n",
+ " \"vessel_class\": \"oil_handymax_mr2\",\n",
+ " }\n",
+ "]\n",
+ "result = AnywhereFreightPricingPriceTimeseries().search(\n",
+ " routes=routes,\n",
+ " time_min=datetime(2024, 1, 1),\n",
+ " time_max=datetime(2024, 3, 31),\n",
+ " frequency=\"day\",\n",
+ " unit=\"usd_per_tonne\",\n",
+ ")\n",
+ "df = result.to_df()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "df.head()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "That's it! You've successfully loaded data using the Vortexa SDK. Check out https://vortechsa.github.io/python-sdk/ for more examples"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.13.13"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/docs/examples/try_me_out/anywhere_freight_pricing_top_ports_destination.ipynb b/docs/examples/try_me_out/anywhere_freight_pricing_top_ports_destination.ipynb
new file mode 100644
index 00000000..265bc573
--- /dev/null
+++ b/docs/examples/try_me_out/anywhere_freight_pricing_top_ports_destination.ipynb
@@ -0,0 +1,79 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Try out the VortexaSDK"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First let's import our requirements"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from vortexasdk import AnywhereFreightPricingTopPortsDestination"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "You'll need to enter your Vortexa API key when prompted."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Get top destination ports for clean products from Houston using MR2 vessels\n",
+ "result = AnywhereFreightPricingTopPortsDestination().search(\n",
+ " origin_id=\"7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030ec6b82f944a73d4da2f\",\n",
+ " vessel_class=\"oil_handymax_mr2\",\n",
+ " product=\"clean\",\n",
+ " unit=\"usd_per_tonne\",\n",
+ ")\n",
+ "df = result.to_df()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "df.head()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "That's it! You've successfully loaded data using the Vortexa SDK. 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.11.0"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/docs/examples/try_me_out/anywhere_freight_pricing_top_ports_origin.ipynb b/docs/examples/try_me_out/anywhere_freight_pricing_top_ports_origin.ipynb
new file mode 100644
index 00000000..ee17a117
--- /dev/null
+++ b/docs/examples/try_me_out/anywhere_freight_pricing_top_ports_origin.ipynb
@@ -0,0 +1,79 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Try out the VortexaSDK"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First let's import our requirements"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from vortexasdk import AnywhereFreightPricingTopPortsOrigin"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "You'll need to enter your Vortexa API key when prompted."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Get top origin ports for clean products to Rotterdam using MR2 vessels\n",
+ "result = AnywhereFreightPricingTopPortsOrigin().search(\n",
+ " destination_id=\"68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e\",\n",
+ " vessel_class=\"oil_handymax_mr2\",\n",
+ " product=\"clean\",\n",
+ " unit=\"usd_per_tonne\",\n",
+ ")\n",
+ "df = result.to_df()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "df.head()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "That's it! You've successfully loaded data using the Vortexa SDK. 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.11.0"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/docs/examples/try_me_out/anywhere_freight_pricing_vessel_classes_details.ipynb b/docs/examples/try_me_out/anywhere_freight_pricing_vessel_classes_details.ipynb
new file mode 100644
index 00000000..b9f9ac58
--- /dev/null
+++ b/docs/examples/try_me_out/anywhere_freight_pricing_vessel_classes_details.ipynb
@@ -0,0 +1,73 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Try out the VortexaSDK"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First let's import our requirements"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from vortexasdk import AnywhereFreightPricingVesselClassesDetails"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "You'll need to enter your Vortexa API key when prompted."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "result = AnywhereFreightPricingVesselClassesDetails().search()\n",
+ "df = result.to_df()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "df.head()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "That's it! You've successfully loaded data using the Vortexa SDK. 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.11.0"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/docs/scripts/serve_docs.sh b/docs/scripts/serve_docs.sh
index f24229ac..a544addf 100755
--- a/docs/scripts/serve_docs.sh
+++ b/docs/scripts/serve_docs.sh
@@ -4,5 +4,7 @@ set -e
. venv/bin/activate
rm -rf ./_build
mkdir -p ./_build/pydocmd/examples
+mkdir -p ./_build/pydocmd/css
+cp -r docs/css/* ./_build/pydocmd/css/
python docs/autogen.py
pydocmd serve
diff --git a/mkdocs.yml b/mkdocs.yml
index c070a07d..b12d67be 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -24,6 +24,16 @@ pages:
Products: endpoints/products.md
Vessels: endpoints/vessels.md
Fixtures: endpoints/fixtures.md
+ Anywhere Freight Pricing:
+ Latest Update: endpoints/anywhere_freight_pricing_latest_update_timestamp.md
+ Time-series: endpoints/anywhere_freight_pricing_price_timeseries.md
+ Price:
+ Get Prices: endpoints/anywhere_freight_pricing_get_price_details.md
+ Post Prices: endpoints/anywhere_freight_pricing_post_price_details.md
+ Top Ports:
+ Origin: endpoints/anywhere_freight_pricing_top_ports_origin.md
+ Destination: endpoints/anywhere_freight_pricing_top_ports_destination.md
+ Vessel Classes: endpoints/anywhere_freight_pricing_vessel_classes_details.md
Vessel Availability:
Search: endpoints/vessel_availability_search.md
Time Series: endpoints/vessel_availability_timeseries.md
@@ -75,3 +85,5 @@ theme:
custom_dir: custom_theme/
name: readthedocs
search_index_only: false
+extra_css:
+ - css/custom.css
diff --git a/pydocmd.yml b/pydocmd.yml
index 90f04d51..2e0835c6 100644
--- a/pydocmd.yml
+++ b/pydocmd.yml
@@ -93,8 +93,22 @@ generate:
- endpoints/voyages_routes_breakdown.md: vortexasdk.endpoints.voyages_routes_breakdown++
- endpoints/voyages_top_hits.md: vortexasdk.endpoints.voyages_top_hits++
- endpoints/voyages_congestion_breakdown.md: vortexasdk.endpoints.voyages_congestion_breakdown++
- - endpoints/refineries.md:
+ - endpoints/refineries.md:
- vortexasdk.endpoints.refineries++
+ - endpoints/anywhere_freight_pricing_latest_update_timestamp.md:
+ - vortexasdk.endpoints.anywhere_freight_pricing_latest_update_timestamp++
+ - endpoints/anywhere_freight_pricing_price_timeseries.md:
+ - vortexasdk.endpoints.anywhere_freight_pricing_price_timeseries++
+ - endpoints/anywhere_freight_pricing_get_price_details.md:
+ - vortexasdk.endpoints.anywhere_freight_pricing_get_price_details++
+ - endpoints/anywhere_freight_pricing_post_price_details.md:
+ - vortexasdk.endpoints.anywhere_freight_pricing_post_price_details++
+ - endpoints/anywhere_freight_pricing_top_ports_origin.md:
+ - vortexasdk.endpoints.anywhere_freight_pricing_top_ports_origin++
+ - endpoints/anywhere_freight_pricing_top_ports_destination.md:
+ - vortexasdk.endpoints.anywhere_freight_pricing_top_ports_destination++
+ - endpoints/anywhere_freight_pricing_vessel_classes_details.md:
+ - vortexasdk.endpoints.anywhere_freight_pricing_vessel_classes_details++
pages:
- Home: index.md << README.md
- Endpoints:
diff --git a/tests/endpoints/test_anywhere_freight_pricing_get_price_details.py b/tests/endpoints/test_anywhere_freight_pricing_get_price_details.py
new file mode 100644
index 00000000..3905558d
--- /dev/null
+++ b/tests/endpoints/test_anywhere_freight_pricing_get_price_details.py
@@ -0,0 +1,57 @@
+from datetime import datetime
+
+from tests.testcases import TestCaseUsingRealAPI
+from vortexasdk import AnywhereFreightPricingGetPriceDetails
+
+
+class TestAnywhereFreightPricingGetPriceDetails(TestCaseUsingRealAPI):
+ def test_search_returns_data(self):
+ result = AnywhereFreightPricingGetPriceDetails().search(
+ time_min=datetime(2024, 1, 1),
+ time_max=datetime(2024, 1, 31),
+ origin_port="7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030ec6b82f944a73d4da2f",
+ destination_port="68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e",
+ vessel_class="oil_aframax_lr2",
+ product="crude",
+ unit="usd_per_tonne",
+ )
+
+ result_list = result.to_list()
+ assert len(result_list) > 0
+ assert "origin_port" in result_list[0]
+ assert "destination_port" in result_list[0]
+ assert "vessel_class" in result_list[0]
+ assert "rates" in result_list[0]
+
+ def test_search_to_df(self):
+ result = AnywhereFreightPricingGetPriceDetails().search(
+ time_min=datetime(2024, 1, 1),
+ time_max=datetime(2024, 1, 31),
+ origin_port="7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030ec6b82f944a73d4da2f",
+ destination_port="68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e",
+ vessel_class="oil_aframax_lr2",
+ product="crude",
+ unit="usd_per_tonne",
+ )
+
+ df = result.to_df()
+ assert len(df) > 0
+ # pd.json_normalize uses dot notation for nested keys
+ assert "origin_port.id" in df.columns
+ assert "destination_port.id" in df.columns
+ assert "vessel_class" in df.columns
+
+ def test_search_with_avoid_zone(self):
+ result = AnywhereFreightPricingGetPriceDetails().search(
+ time_min=datetime(2024, 1, 1),
+ time_max=datetime(2024, 1, 31),
+ origin_port="7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030ec6b82f944a73d4da2f",
+ destination_port="68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e",
+ vessel_class="oil_aframax_lr2",
+ product="crude",
+ unit="usd_per_tonne",
+ avoid_zone=["Suez Canal"],
+ )
+
+ result_list = result.to_list()
+ assert len(result_list) > 0
diff --git a/tests/endpoints/test_anywhere_freight_pricing_latest_update_timestamp.py b/tests/endpoints/test_anywhere_freight_pricing_latest_update_timestamp.py
new file mode 100644
index 00000000..03fae85e
--- /dev/null
+++ b/tests/endpoints/test_anywhere_freight_pricing_latest_update_timestamp.py
@@ -0,0 +1,8 @@
+from tests.testcases import TestCaseUsingRealAPI
+from vortexasdk import AnywhereFreightPricingLatestUpdateTimestamp
+
+
+class TestAnywhereFreightPricingLatestUpdateTimestamp(TestCaseUsingRealAPI):
+ def test_search_returns_timestamp(self):
+ result = AnywhereFreightPricingLatestUpdateTimestamp().search()
+ assert "timestamp" in result
diff --git a/tests/endpoints/test_anywhere_freight_pricing_post_price_details.py b/tests/endpoints/test_anywhere_freight_pricing_post_price_details.py
new file mode 100644
index 00000000..20423acf
--- /dev/null
+++ b/tests/endpoints/test_anywhere_freight_pricing_post_price_details.py
@@ -0,0 +1,101 @@
+from datetime import datetime
+
+from tests.testcases import TestCaseUsingRealAPI
+from vortexasdk import AnywhereFreightPricingPostPriceDetails
+
+
+class TestAnywhereFreightPricingPostPriceDetails(TestCaseUsingRealAPI):
+ def test_search_returns_data(self):
+ routes = [
+ {
+ "origin_port": "7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030ec6b82f944a73d4da2f",
+ "destination_port": "68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e",
+ "product": "crude",
+ "vessel_class": "oil_aframax_lr2",
+ }
+ ]
+
+ result = AnywhereFreightPricingPostPriceDetails().search(
+ routes=routes,
+ time_min=datetime(2024, 1, 1),
+ time_max=datetime(2024, 1, 31),
+ unit="usd_per_tonne",
+ )
+
+ result_list = result.to_list()
+ assert len(result_list) > 0
+ assert "origin_port" in result_list[0]
+ assert "destination_port" in result_list[0]
+ assert "vessel_class" in result_list[0]
+ assert "rates" in result_list[0]
+
+ def test_search_to_df(self):
+ routes = [
+ {
+ "origin_port": "7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030ec6b82f944a73d4da2f",
+ "destination_port": "68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e",
+ "product": "crude",
+ "vessel_class": "oil_aframax_lr2",
+ }
+ ]
+
+ result = AnywhereFreightPricingPostPriceDetails().search(
+ routes=routes,
+ time_min=datetime(2024, 1, 1),
+ time_max=datetime(2024, 1, 31),
+ unit="usd_per_tonne",
+ )
+
+ df = result.to_df()
+ assert len(df) > 0
+ # pd.json_normalize uses dot notation for nested keys
+ assert "origin_port.id" in df.columns
+ assert "destination_port.id" in df.columns
+ assert "vessel_class" in df.columns
+
+ def test_search_multiple_routes(self):
+ routes = [
+ {
+ "origin_port": "7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030ec6b82f944a73d4da2f",
+ "destination_port": "68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e",
+ "product": "crude",
+ "vessel_class": "oil_aframax_lr2",
+ },
+ {
+ "origin_port": "68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e",
+ "destination_port": "ea4921c8ad4fddb5fe3e7a4f834c1aa5863e43283c73da5f02d93bbc5dba72eb",
+ "product": "clean",
+ "vessel_class": "oil_handymax_mr2",
+ },
+ ]
+
+ result = AnywhereFreightPricingPostPriceDetails().search(
+ routes=routes,
+ time_min=datetime(2024, 1, 1),
+ time_max=datetime(2024, 1, 31),
+ unit="usd_per_tonne",
+ )
+
+ result_list = result.to_list()
+ assert len(result_list) >= 2
+
+ def test_search_with_avoid_zone(self):
+ routes = [
+ {
+ "origin_port": "7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030ec6b82f944a73d4da2f",
+ "destination_port": "68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e",
+ "product": "crude",
+ "vessel_class": "oil_aframax_lr2",
+ "avoid_zone": ["Suez Canal"],
+ }
+ ]
+
+ result = AnywhereFreightPricingPostPriceDetails().search(
+ routes=routes,
+ time_min=datetime(2024, 1, 1),
+ time_max=datetime(2024, 1, 31),
+ unit="usd_per_tonne",
+ )
+
+ result_list = result.to_list()
+ assert len(result_list) > 0
diff --git a/tests/endpoints/test_anywhere_freight_pricing_price_timeseries.py b/tests/endpoints/test_anywhere_freight_pricing_price_timeseries.py
new file mode 100644
index 00000000..6355ac53
--- /dev/null
+++ b/tests/endpoints/test_anywhere_freight_pricing_price_timeseries.py
@@ -0,0 +1,55 @@
+from datetime import datetime
+
+from tests.testcases import TestCaseUsingRealAPI
+from vortexasdk import AnywhereFreightPricingPriceTimeseries
+
+
+class TestAnywhereFreightPricingPriceTimeseries(TestCaseUsingRealAPI):
+ def test_search_returns_data(self):
+ routes = [
+ {
+ "origin_port": "68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e",
+ "destination_port": "ea4921c8ad4fddb5fe3e7a4f834c1aa5863e43283c73da5f02d93bbc5dba72eb",
+ "product": "clean",
+ "vessel_class": "oil_handymax_mr2",
+ }
+ ]
+
+ result = AnywhereFreightPricingPriceTimeseries().search(
+ routes=routes,
+ time_min=datetime(2026, 2, 20),
+ time_max=datetime(2026, 5, 20),
+ frequency="month",
+ unit="usd_per_tonne",
+ )
+
+ result_list = result.to_list()
+ assert len(result_list) > 0
+ assert "origin_port" in result_list[0]
+ assert "destination_port" in result_list[0]
+ assert "prices" in result_list[0]
+
+ def test_search_to_df(self):
+ routes = [
+ {
+ "origin_port": "68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e",
+ "destination_port": "ea4921c8ad4fddb5fe3e7a4f834c1aa5863e43283c73da5f02d93bbc5dba72eb",
+ "product": "clean",
+ "vessel_class": "oil_handymax_mr2",
+ }
+ ]
+
+ result = AnywhereFreightPricingPriceTimeseries().search(
+ routes=routes,
+ time_min=datetime(2026, 2, 20),
+ time_max=datetime(2026, 5, 20),
+ frequency="month",
+ unit="usd_per_tonne",
+ )
+
+ df = result.to_df()
+ assert len(df) > 0
+ # Top-level keys preserved, nested arrays like prices stay as lists
+ assert "origin_port" in df.columns
+ assert "destination_port" in df.columns
+ assert "vessel_class" in df.columns
diff --git a/tests/endpoints/test_anywhere_freight_pricing_top_ports_destination.py b/tests/endpoints/test_anywhere_freight_pricing_top_ports_destination.py
new file mode 100644
index 00000000..2955f047
--- /dev/null
+++ b/tests/endpoints/test_anywhere_freight_pricing_top_ports_destination.py
@@ -0,0 +1,45 @@
+from tests.testcases import TestCaseUsingRealAPI
+from vortexasdk import AnywhereFreightPricingTopPortsDestination
+
+
+class TestAnywhereFreightPricingTopPortsDestination(TestCaseUsingRealAPI):
+ def test_search_returns_data(self):
+ result = AnywhereFreightPricingTopPortsDestination().search(
+ origin_id="7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030ec6b82f944a73d4da2f",
+ vessel_class="oil_handymax_mr2",
+ product="clean",
+ unit="usd_per_tonne",
+ )
+
+ result_list = result.to_list()
+ assert len(result_list) > 0
+ assert "geography" in result_list[0]
+ assert "price_details" in result_list[0]
+ assert "id" in result_list[0]["geography"]
+ assert "name" in result_list[0]["geography"]
+
+ def test_search_to_df(self):
+ result = AnywhereFreightPricingTopPortsDestination().search(
+ origin_id="7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030ec6b82f944a73d4da2f",
+ vessel_class="oil_handymax_mr2",
+ product="clean",
+ unit="usd_per_tonne",
+ )
+
+ df = result.to_df()
+ assert len(df) > 0
+ # pd.json_normalize uses dot notation for nested keys
+ assert "geography.id" in df.columns
+ assert "geography.name" in df.columns
+
+ def test_search_with_avoid_zone(self):
+ result = AnywhereFreightPricingTopPortsDestination().search(
+ origin_id="7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030ec6b82f944a73d4da2f",
+ vessel_class="oil_handymax_mr2",
+ product="clean",
+ unit="usd_per_tonne",
+ avoid_zone=["Suez Canal"],
+ )
+
+ result_list = result.to_list()
+ assert len(result_list) > 0
diff --git a/tests/endpoints/test_anywhere_freight_pricing_top_ports_origin.py b/tests/endpoints/test_anywhere_freight_pricing_top_ports_origin.py
new file mode 100644
index 00000000..7e698ab1
--- /dev/null
+++ b/tests/endpoints/test_anywhere_freight_pricing_top_ports_origin.py
@@ -0,0 +1,45 @@
+from tests.testcases import TestCaseUsingRealAPI
+from vortexasdk import AnywhereFreightPricingTopPortsOrigin
+
+
+class TestAnywhereFreightPricingTopPortsOrigin(TestCaseUsingRealAPI):
+ def test_search_returns_data(self):
+ result = AnywhereFreightPricingTopPortsOrigin().search(
+ destination_id="68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e",
+ vessel_class="oil_handymax_mr2",
+ product="clean",
+ unit="usd_per_tonne",
+ )
+
+ result_list = result.to_list()
+ assert len(result_list) > 0
+ assert "geography" in result_list[0]
+ assert "price_details" in result_list[0]
+ assert "id" in result_list[0]["geography"]
+ assert "name" in result_list[0]["geography"]
+
+ def test_search_to_df(self):
+ result = AnywhereFreightPricingTopPortsOrigin().search(
+ destination_id="68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e",
+ vessel_class="oil_handymax_mr2",
+ product="clean",
+ unit="usd_per_tonne",
+ )
+
+ df = result.to_df()
+ assert len(df) > 0
+ # pd.json_normalize uses dot notation for nested keys
+ assert "geography.id" in df.columns
+ assert "geography.name" in df.columns
+
+ def test_search_with_avoid_zone(self):
+ result = AnywhereFreightPricingTopPortsOrigin().search(
+ destination_id="68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e",
+ vessel_class="oil_handymax_mr2",
+ product="clean",
+ unit="usd_per_tonne",
+ avoid_zone=["Suez Canal"],
+ )
+
+ result_list = result.to_list()
+ assert len(result_list) > 0
diff --git a/tests/endpoints/test_anywhere_freight_pricing_vessel_classes_details.py b/tests/endpoints/test_anywhere_freight_pricing_vessel_classes_details.py
new file mode 100644
index 00000000..4e4dbc8e
--- /dev/null
+++ b/tests/endpoints/test_anywhere_freight_pricing_vessel_classes_details.py
@@ -0,0 +1,24 @@
+from tests.testcases import TestCaseUsingRealAPI
+from vortexasdk import AnywhereFreightPricingVesselClassesDetails
+
+
+class TestAnywhereFreightPricingVesselClassesDetails(TestCaseUsingRealAPI):
+ def test_search_returns_data(self):
+ result = AnywhereFreightPricingVesselClassesDetails().search()
+
+ result_list = result.to_list()
+ assert len(result_list) > 0
+ assert "name" in result_list[0]
+ assert "suggested_tonnage" in result_list[0]
+ assert "min_tonnage" in result_list[0]
+ assert "max_tonnage" in result_list[0]
+
+ def test_search_to_df(self):
+ result = AnywhereFreightPricingVesselClassesDetails().search()
+
+ df = result.to_df()
+ assert len(df) > 0
+ assert "name" in df.columns
+ assert "suggested_tonnage" in df.columns
+ assert "min_tonnage" in df.columns
+ assert "max_tonnage" in df.columns
diff --git a/vortexasdk/__init__.py b/vortexasdk/__init__.py
index fc28ab34..2684aa3e 100644
--- a/vortexasdk/__init__.py
+++ b/vortexasdk/__init__.py
@@ -40,6 +40,20 @@
VesselSummary,
VesselPositions,
Refineries,
+ AnywhereFreightPricingLatestUpdateTimestamp,
+ AnywhereFreightPricingPriceTimeseries,
+ AnywhereFreightPricingGetPriceDetails,
+ AnywhereFreightPricingPostPriceDetails,
+ AnywhereFreightPricingTopPortsDestination,
+ AnywhereFreightPricingTopPortsOrigin,
+ AnywhereFreightPricingVesselClassesDetails,
+ # AFP types
+ AfpAvoidZone,
+ AfpFrequency,
+ AfpProduct,
+ AfpRoute,
+ AfpUnit,
+ AfpVesselClass,
)
# noinspection PyUnresolvedReferences
@@ -88,6 +102,20 @@
"VesselSummary",
"VesselPositions",
"Refineries",
+ "AnywhereFreightPricingLatestUpdateTimestamp",
+ "AnywhereFreightPricingPriceTimeseries",
+ "AnywhereFreightPricingGetPriceDetails",
+ "AnywhereFreightPricingPostPriceDetails",
+ "AnywhereFreightPricingTopPortsDestination",
+ "AnywhereFreightPricingTopPortsOrigin",
+ "AnywhereFreightPricingVesselClassesDetails",
+ # AFP types
+ "AfpAvoidZone",
+ "AfpFrequency",
+ "AfpProduct",
+ "AfpRoute",
+ "AfpUnit",
+ "AfpVesselClass",
"__version__",
"run_all_checks",
]
diff --git a/vortexasdk/api/shared_types.py b/vortexasdk/api/shared_types.py
index b8171f39..fe4d94b7 100644
--- a/vortexasdk/api/shared_types.py
+++ b/vortexasdk/api/shared_types.py
@@ -20,6 +20,16 @@ def to_ISODate_Array(days: List[datetime]) -> List[ISODate]:
return [to_ISODate(date) for date in days]
+def to_date_string(dt: datetime) -> str:
+ """
+ Convert datetime to YYYY-MM-DD date string.
+
+ The Anywhere Freight Pricing (AFP) API requires date-only strings without
+ time components, unlike other Vortexa endpoints which use full ISO-8601.
+ """
+ return dt.strftime("%Y-%m-%d")
+
+
class EntityWithSingleLayer(BaseModel):
"""Holds commonly used properties."""
diff --git a/vortexasdk/client.py b/vortexasdk/client.py
index f57c9c23..b1ee4efa 100644
--- a/vortexasdk/client.py
+++ b/vortexasdk/client.py
@@ -126,8 +126,10 @@ def _create_url(self, path: str) -> str:
f"{API_URL}{path}?_sdk=python_v{__version__}&apikey={self.api_key}"
)
- def _create_url_with_params(self, path: str, params: Dict) -> str:
- stringParams = urlencode(params)
+ def _create_url_with_params(
+ self, path: str, params: Dict, doseq: bool = False
+ ) -> str:
+ stringParams = urlencode(params, doseq=doseq)
if len(stringParams) > 0:
return f"{API_URL}{path}?_sdk=python_v{__version__}&apikey={self.api_key}&{stringParams}"
else:
diff --git a/vortexasdk/endpoints/__init__.py b/vortexasdk/endpoints/__init__.py
index 8db54fc8..88a54583 100644
--- a/vortexasdk/endpoints/__init__.py
+++ b/vortexasdk/endpoints/__init__.py
@@ -68,6 +68,35 @@
from vortexasdk.endpoints.vessel_positions import VesselPositions
from vortexasdk.endpoints.refineries import Refineries
+from vortexasdk.endpoints.anywhere_freight_pricing_latest_update_timestamp import (
+ AnywhereFreightPricingLatestUpdateTimestamp,
+)
+from vortexasdk.endpoints.anywhere_freight_pricing_price_timeseries import (
+ AnywhereFreightPricingPriceTimeseries,
+)
+from vortexasdk.endpoints.anywhere_freight_pricing_get_price_details import (
+ AnywhereFreightPricingGetPriceDetails,
+)
+from vortexasdk.endpoints.anywhere_freight_pricing_post_price_details import (
+ AnywhereFreightPricingPostPriceDetails,
+)
+from vortexasdk.endpoints.anywhere_freight_pricing_top_ports_destination import (
+ AnywhereFreightPricingTopPortsDestination,
+)
+from vortexasdk.endpoints.anywhere_freight_pricing_top_ports_origin import (
+ AnywhereFreightPricingTopPortsOrigin,
+)
+from vortexasdk.endpoints.anywhere_freight_pricing_vessel_classes_details import (
+ AnywhereFreightPricingVesselClassesDetails,
+)
+from vortexasdk.endpoints.anywhere_freight_pricing_types import (
+ AfpAvoidZone,
+ AfpFrequency,
+ AfpProduct,
+ AfpRoute,
+ AfpUnit,
+ AfpVesselClass,
+)
# Explicitly list all exported classes, to help MyPy know what is available
__all__ = [
@@ -109,4 +138,18 @@
"VesselSummary",
"VesselPositions",
"Refineries",
+ "AnywhereFreightPricingLatestUpdateTimestamp",
+ "AnywhereFreightPricingPriceTimeseries",
+ "AnywhereFreightPricingGetPriceDetails",
+ "AnywhereFreightPricingPostPriceDetails",
+ "AnywhereFreightPricingTopPortsDestination",
+ "AnywhereFreightPricingTopPortsOrigin",
+ "AnywhereFreightPricingVesselClassesDetails",
+ # AFP types for user type annotations
+ "AfpAvoidZone",
+ "AfpFrequency",
+ "AfpProduct",
+ "AfpRoute",
+ "AfpUnit",
+ "AfpVesselClass",
]
diff --git a/vortexasdk/endpoints/anywhere_freight_pricing_get_price_details.py b/vortexasdk/endpoints/anywhere_freight_pricing_get_price_details.py
new file mode 100644
index 00000000..36748b5c
--- /dev/null
+++ b/vortexasdk/endpoints/anywhere_freight_pricing_get_price_details.py
@@ -0,0 +1,149 @@
+"""
+Try me out in your browser:
+
+[](https://mybinder.org/v2/gh/VorTECHsa/python-sdk/master?filepath=docs%2Fexamples%2Ftry_me_out%2Fanywhere_freight_pricing_get_price_details.ipynb)
+"""
+from datetime import datetime
+from typing import Any, Dict, List, Optional
+
+from vortexasdk.client import default_client, _handle_response
+from vortexasdk.endpoints.endpoints import (
+ ANYWHERE_FREIGHT_PRICING_PRICE_DETAILS,
+)
+from vortexasdk.endpoints.anywhere_freight_pricing_result import (
+ AnywhereFreightPricingResult,
+)
+from vortexasdk.endpoints.anywhere_freight_pricing_types import (
+ AfpAvoidZone,
+ AfpProduct,
+ AfpUnit,
+ AfpVesselClass,
+)
+from vortexasdk.logger import get_logger
+from vortexasdk.retry_session import retry_get
+from vortexasdk.api.shared_types import to_date_string
+
+logger = get_logger(__name__)
+
+
+class AnywhereFreightPricingGetPriceDetails:
+ """
+ Anywhere Freight Pricing Get Price Details endpoint.
+
+ Given a set of details about a single route (origin, destination, etc),
+ this will find rates, lumpsums and prediction confidence of the route.
+
+ Please note: you will require a subscription to our Anywhere Freight Pricing
+ module to access this endpoint.
+ """
+
+ def __init__(self) -> None:
+ self._resource = ANYWHERE_FREIGHT_PRICING_PRICE_DETAILS
+
+ def search(
+ self,
+ time_min: datetime,
+ time_max: datetime,
+ origin_port: str,
+ destination_port: str,
+ vessel_class: AfpVesselClass,
+ product: AfpProduct,
+ unit: AfpUnit = "usd_per_tonne",
+ avoid_zone: Optional[List[AfpAvoidZone]] = None,
+ suggested_tonnage: Optional[float] = None,
+ ) -> AnywhereFreightPricingResult:
+ """
+ List prices of a route.
+
+ Given a set of details about a single route (origin, destination, etc),
+ this will find rates, lumpsums and prediction confidence of the route.
+
+ # Arguments
+
+ time_min: The UTC start date of the time filter.
+
+ time_max: The UTC end date of the time filter.
+
+ origin_port: Geographic ID of the origin port.
+
+ destination_port: Geographic ID of the destination port.
+
+ vessel_class: The vessel class for the route. Must be one of:
+ `'oil_coastal'`, `'oil_specialised'`, `'oil_handysize_mr1'`,
+ `'oil_handymax_mr2'`, `'oil_panamax_lr1'`, `'oil_aframax_lr2'`,
+ `'oil_suezmax_lr3'`, `'oil_vlcc'`.
+
+ product: The product type. Must be one of: `'clean'`, `'dirty'`, `'crude'`.
+
+ unit: The unit for pricing. Must be one of: `'usd_per_tonne'`, `'usd_per_barrel'`.
+ Defaults to `'usd_per_tonne'`.
+
+ avoid_zone: Routing zones to avoid for this route. Options:
+ `'Panama Canal'`, `'Suez Canal'`.
+
+ suggested_tonnage: Override the default suggested tonnage Vortexa will
+ suggest based on the vessel class.
+
+ # Returns
+ `AnywhereFreightPricingResult`
+
+ # Example
+ _Get price details for an Aframax LR2 crude route from Houston to Rotterdam._
+
+ ```python
+ >>> from vortexasdk import AnywhereFreightPricingGetPriceDetails
+ >>> from datetime import datetime
+ >>> result = AnywhereFreightPricingGetPriceDetails().search(
+ ... time_min=datetime(2024, 1, 1),
+ ... time_max=datetime(2024, 12, 31),
+ ... origin_port="7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030ec6b82f944a73d4da2f",
+ ... destination_port="68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e",
+ ... vessel_class="oil_aframax_lr2",
+ ... product="crude",
+ ... unit="usd_per_tonne",
+ ... )
+ >>> df = result.to_df()
+
+ ```
+
+ Returns a DataFrame with columns including rates, lumpsums, and confidence values:
+
+ | | date | rate | lumpsum | confidence |
+ |---:|:-----------|------:|----------:|-----------:|
+ | 0 | 2024-01-01 | 12.50 | 1250000.0 | 2 |
+ | 1 | 2024-01-02 | 12.75 | 1275000.0 | 2 |
+
+ """
+ logger.info(
+ f"Fetching Anywhere Freight Pricing price details for route "
+ f"{origin_port} -> {destination_port}"
+ )
+
+ params: Dict[str, Any] = {
+ "time_min": to_date_string(time_min),
+ "time_max": to_date_string(time_max),
+ "origin_port": origin_port,
+ "destination_port": destination_port,
+ "vessel_class": vessel_class,
+ "product": product,
+ "unit": unit,
+ }
+
+ if avoid_zone is not None:
+ params["avoid_zone"] = avoid_zone
+
+ if suggested_tonnage is not None:
+ params["suggested_tonnage"] = suggested_tonnage
+
+ client = default_client()
+ url = client._create_url_with_params(
+ self._resource, params, doseq=True
+ )
+
+ response = retry_get(url)
+
+ data = _handle_response(response)
+ return AnywhereFreightPricingResult(
+ records=data.get("data", []),
+ reference=data.get("metadata", {}),
+ )
diff --git a/vortexasdk/endpoints/anywhere_freight_pricing_latest_update_timestamp.py b/vortexasdk/endpoints/anywhere_freight_pricing_latest_update_timestamp.py
new file mode 100644
index 00000000..9a66daca
--- /dev/null
+++ b/vortexasdk/endpoints/anywhere_freight_pricing_latest_update_timestamp.py
@@ -0,0 +1,63 @@
+"""
+Try me out in your browser:
+
+[](https://mybinder.org/v2/gh/VorTECHsa/python-sdk/master?filepath=docs%2Fexamples%2Ftry_me_out%2Fanywhere_freight_pricing_latest_update_timestamp.ipynb)
+"""
+from typing import Dict
+
+from vortexasdk.client import default_client, _handle_response
+from vortexasdk.endpoints.endpoints import (
+ ANYWHERE_FREIGHT_PRICING_LATEST_UPDATE,
+)
+from vortexasdk.logger import get_logger
+from vortexasdk.retry_session import retry_get
+
+logger = get_logger(__name__)
+
+
+class AnywhereFreightPricingLatestUpdateTimestamp:
+ """
+ Anywhere Freight Pricing Latest Update Timestamp endpoint.
+
+ Please note: you will require a subscription to our Anywhere Freight Pricing
+ module to access this endpoint.
+ """
+
+ def __init__(self) -> None:
+ self._resource = ANYWHERE_FREIGHT_PRICING_LATEST_UPDATE
+
+ def search(self) -> Dict[str, str]:
+ """
+ Get the time the predicted route prices were last updated.
+
+ # Returns
+ A dictionary containing a `timestamp` key with the ISO 8601 formatted
+ date-time string of when the pricing predictions were last updated.
+
+ # Example
+ _Get the latest update timestamp for Anywhere Freight Pricing._
+
+ ```python
+ >>> from vortexasdk import AnywhereFreightPricingLatestUpdateTimestamp
+ >>> result = AnywhereFreightPricingLatestUpdateTimestamp().search()
+
+ ```
+
+ Returns:
+
+ ```
+ {
+ 'timestamp': '2025-01-30T14:40:06.803Z'
+ }
+ ```
+
+ """
+ logger.info(
+ "Fetching Anywhere Freight Pricing latest update timestamp"
+ )
+
+ client = default_client()
+ url = client._create_url(self._resource)
+ response = retry_get(url)
+
+ return _handle_response(response)
diff --git a/vortexasdk/endpoints/anywhere_freight_pricing_post_price_details.py b/vortexasdk/endpoints/anywhere_freight_pricing_post_price_details.py
new file mode 100644
index 00000000..e156ed6a
--- /dev/null
+++ b/vortexasdk/endpoints/anywhere_freight_pricing_post_price_details.py
@@ -0,0 +1,127 @@
+"""
+Try me out in your browser:
+
+[](https://mybinder.org/v2/gh/VorTECHsa/python-sdk/master?filepath=docs%2Fexamples%2Ftry_me_out%2Fanywhere_freight_pricing_post_price_details.ipynb)
+"""
+from datetime import datetime
+from typing import Any, Dict, List
+
+from vortexasdk.endpoints.endpoints import (
+ ANYWHERE_FREIGHT_PRICING_PRICE_DETAILS,
+)
+from vortexasdk.endpoints.anywhere_freight_pricing_result import (
+ AnywhereFreightPricingResult,
+)
+from vortexasdk.endpoints.anywhere_freight_pricing_types import (
+ AfpRoute,
+ AfpUnit,
+)
+from vortexasdk.logger import get_logger
+from vortexasdk.operations import Search
+from vortexasdk.api.shared_types import to_date_string
+
+logger = get_logger(__name__)
+
+
+class AnywhereFreightPricingPostPriceDetails(Search):
+ """
+ Anywhere Freight Pricing Post Price Details endpoint.
+
+ Given a set of details about multiple routes (origin, destination, etc),
+ this will find rates, lumpsums and prediction confidence for each route.
+
+ Please note: you will require a subscription to our Anywhere Freight Pricing
+ module to access this endpoint.
+ """
+
+ def __init__(self) -> None:
+ Search.__init__(self, ANYWHERE_FREIGHT_PRICING_PRICE_DETAILS)
+
+ def search(
+ self,
+ routes: List[AfpRoute],
+ time_min: datetime,
+ time_max: datetime,
+ unit: AfpUnit = "usd_per_tonne",
+ ) -> AnywhereFreightPricingResult:
+ """
+ List prices for multiple routes.
+
+ Given a set of details about multiple routes (origin, destination, etc),
+ this will find rates, lumpsums and prediction confidence for each route.
+
+ # Arguments
+
+ routes: A list of route dictionaries. Each route must contain:
+ - `origin_port` (str, required): Geographical ID of the origin port.
+ - `destination_port` (str, required): Geographical ID of the destination port.
+ - `product` (str, required): One of `'clean'`, `'dirty'`, `'crude'`.
+ - `vessel_class` (str, required): One of `'oil_coastal'`, `'oil_specialised'`,
+ `'oil_handysize_mr1'`, `'oil_handymax_mr2'`, `'oil_panamax_lr1'`,
+ `'oil_aframax_lr2'`, `'oil_suezmax_lr3'`, `'oil_vlcc'`.
+ - `avoid_zone` (list, optional): Routing zones to avoid. Options:
+ `'Panama Canal'`, `'Suez Canal'`.
+ - `suggested_tonnage` (float, optional): Suggested tonnage for the route.
+
+ time_min: The UTC start date of the time filter.
+
+ time_max: The UTC end date of the time filter.
+
+ unit: The unit for pricing. Must be one of: `'usd_per_tonne'`, `'usd_per_barrel'`.
+ Defaults to `'usd_per_tonne'`.
+
+ # Returns
+ `AnywhereFreightPricingResult`
+
+ # Example
+ _Get price details for multiple routes._
+
+ ```python
+ >>> from vortexasdk import AnywhereFreightPricingPostPriceDetails
+ >>> from datetime import datetime
+ >>> routes = [
+ ... {
+ ... "origin_port": "7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030ec6b82f944a73d4da2f",
+ ... "destination_port": "68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e",
+ ... "product": "crude",
+ ... "vessel_class": "oil_aframax_lr2",
+ ... },
+ ... {
+ ... "origin_port": "68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e",
+ ... "destination_port": "ea4921c8ad4fddb5fe3e7a4f834c1aa5863e43283c73da5f02d93bbc5dba72eb",
+ ... "product": "clean",
+ ... "vessel_class": "oil_handymax_mr2",
+ ... }
+ ... ]
+ >>> result = AnywhereFreightPricingPostPriceDetails().search(
+ ... routes=routes,
+ ... time_min=datetime(2024, 1, 1),
+ ... time_max=datetime(2024, 1, 31),
+ ... unit="usd_per_tonne",
+ ... )
+ >>> df = result.to_df()
+
+ ```
+
+ Returns a DataFrame with columns including rates, lumpsums, and confidence values:
+
+ | | date | rate | lumpsum | confidence |
+ |---:|:-----------|------:|----------:|-----------:|
+ | 0 | 2024-01-01 | 12.50 | 1250000.0 | 2 |
+ | 1 | 2024-01-02 | 12.75 | 1275000.0 | 2 |
+
+ """
+ api_params: Dict[str, Any] = {
+ "routes": routes,
+ "time_min": to_date_string(time_min),
+ "time_max": to_date_string(time_max),
+ "unit": unit,
+ }
+
+ response = super().search_with_client(
+ response_type="breakdown", **api_params
+ )
+
+ return AnywhereFreightPricingResult(
+ records=response["data"], reference=response.get("reference", {})
+ )
diff --git a/vortexasdk/endpoints/anywhere_freight_pricing_price_timeseries.py b/vortexasdk/endpoints/anywhere_freight_pricing_price_timeseries.py
new file mode 100644
index 00000000..53332368
--- /dev/null
+++ b/vortexasdk/endpoints/anywhere_freight_pricing_price_timeseries.py
@@ -0,0 +1,127 @@
+"""
+Try me out in your browser:
+
+[](https://mybinder.org/v2/gh/VorTECHsa/python-sdk/master?filepath=docs%2Fexamples%2Ftry_me_out%2Fanywhere_freight_pricing_price_timeseries.ipynb)
+"""
+from datetime import datetime
+from typing import Any, Dict, List
+
+from vortexasdk.endpoints.endpoints import (
+ ANYWHERE_FREIGHT_PRICING_PRICE_TIMESERIES,
+)
+from vortexasdk.endpoints.anywhere_freight_pricing_result import (
+ AnywhereFreightPricingResult,
+)
+from vortexasdk.endpoints.anywhere_freight_pricing_types import (
+ AfpFrequency,
+ AfpRoute,
+ AfpUnit,
+)
+from vortexasdk.logger import get_logger
+from vortexasdk.operations import Search
+from vortexasdk.api.shared_types import to_date_string
+
+logger = get_logger(__name__)
+
+
+class AnywhereFreightPricingPriceTimeseries(Search):
+ """
+ Anywhere Freight Pricing Price Timeseries endpoint.
+
+ Please note: you will require a subscription to our Anywhere Freight Pricing
+ module to access this endpoint.
+ """
+
+ def __init__(self) -> None:
+ Search.__init__(self, ANYWHERE_FREIGHT_PRICING_PRICE_TIMESERIES)
+
+ def search(
+ self,
+ routes: List[AfpRoute],
+ time_min: datetime,
+ time_max: datetime,
+ frequency: AfpFrequency = "month",
+ unit: AfpUnit = "usd_per_tonne",
+ ) -> AnywhereFreightPricingResult:
+ """
+ Get historical pricing over time for multiple routes.
+
+ Given a set of details about multiple routes (origin, destination, etc),
+ a time period and frequency, this returns historical pricing over time
+ bucketed by the chosen frequency.
+
+ # Arguments
+
+ routes: A list of route dictionaries. Each route must contain:
+ - `origin_port` (str, required): Geographical ID of the origin port.
+ - `destination_port` (str, required): Geographical ID of the destination port.
+ - `product` (str, required): One of `'clean'`, `'dirty'`, `'crude'`.
+ - `vessel_class` (str, required): One of `'oil_coastal'`, `'oil_specialised'`,
+ `'oil_handysize_mr1'`, `'oil_handymax_mr2'`, `'oil_panamax_lr1'`,
+ `'oil_aframax_lr2'`, `'oil_suezmax_lr3'`, `'oil_vlcc'`.
+ - `avoid_zone` (list, optional): Routing zones to avoid. Options:
+ `'Panama Canal'`, `'Suez Canal'`.
+ - `suggested_tonnage` (float, optional): Suggested tonnage for the route.
+
+ time_min: The UTC start date of the time filter.
+
+ time_max: The UTC end date of the time filter.
+
+ frequency: Frequency denoting the granularity of the time series.
+ Must be one of: `'day'`, `'week'`, `'doe_week'`, `'month'`, `'quarter'`, `'year'`.
+ Defaults to `'month'`.
+
+ unit: The unit for pricing. Must be one of: `'usd_per_tonne'`, `'usd_per_barrel'`.
+ Defaults to `'usd_per_tonne'`.
+
+ # Returns
+ `AnywhereFreightPricingResult`
+
+ # Example
+ _Get daily pricing for a Handymax MR2 clean route from Rotterdam to New York._
+
+ ```python
+ >>> from vortexasdk import AnywhereFreightPricingPriceTimeseries
+ >>> from datetime import datetime
+ >>> routes = [
+ ... {
+ ... "origin_port": "68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e",
+ ... "destination_port": "ea4921c8ad4fddb5fe3e7a4f834c1aa5863e43283c73da5f02d93bbc5dba72eb",
+ ... "product": "clean",
+ ... "vessel_class": "oil_handymax_mr2",
+ ... }
+ ... ]
+ >>> result = AnywhereFreightPricingPriceTimeseries().search(
+ ... routes=routes,
+ ... time_min=datetime(2026, 2, 20),
+ ... time_max=datetime(2026, 5, 20),
+ ... frequency="day",
+ ... unit="usd_per_tonne",
+ ... )
+ >>> df = result.to_df()
+
+ ```
+
+ Returns a DataFrame with columns:
+
+ | | origin_port | destination_port | vessel_class | product | date | price | price_lower | price_upper | voyage_price |
+ |---:|:------------|:-----------------|:---------------------|:--------|:-----------|------:|------------:|------------:|-------------- :|
+ | 0 | 68faf65a... | ea4921c8... | oil_handymax_mr2 | clean | 2026-02-20 | 15.50 | 14.00 | 17.00 | 16.223888 |
+ | 1 | 68faf65a... | ea4921c8... | oil_handymax_mr2 | clean | 2026-02-21 | 16.20 | 14.80 | 17.60 | 16.223888 |
+
+ """
+ api_params: Dict[str, Any] = {
+ "routes": routes,
+ "time_min": to_date_string(time_min),
+ "time_max": to_date_string(time_max),
+ "frequency": frequency,
+ "unit": unit,
+ }
+
+ response = super().search_with_client(
+ response_type="breakdown", **api_params
+ )
+
+ return AnywhereFreightPricingResult(
+ records=response["data"], reference=response.get("reference", {})
+ )
diff --git a/vortexasdk/endpoints/anywhere_freight_pricing_result.py b/vortexasdk/endpoints/anywhere_freight_pricing_result.py
new file mode 100644
index 00000000..7262e2bd
--- /dev/null
+++ b/vortexasdk/endpoints/anywhere_freight_pricing_result.py
@@ -0,0 +1,36 @@
+from typing import List, Optional, Union
+
+import pandas as pd
+from typing_extensions import Literal
+
+from vortexasdk.api.search_result import Result
+
+
+class AnywhereFreightPricingResult(Result):
+ """Container class that holds results from Anywhere Freight Pricing endpoints."""
+
+ def to_df(
+ self, columns: Optional[Union[List[str], Literal["all"]]] = "all"
+ ) -> pd.DataFrame:
+ """
+ Represent the results as a 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 the results, using json_normalize to flatten nested structures.
+ """
+ if not self.records:
+ return pd.DataFrame()
+
+ df = pd.json_normalize(self.records)
+
+ if columns is None or columns == "all":
+ return df
+
+ # Filter to requested columns that exist in the dataframe
+ available_columns = [col for col in columns if col in df.columns]
+ return df[available_columns]
diff --git a/vortexasdk/endpoints/anywhere_freight_pricing_top_ports_destination.py b/vortexasdk/endpoints/anywhere_freight_pricing_top_ports_destination.py
new file mode 100644
index 00000000..2819b512
--- /dev/null
+++ b/vortexasdk/endpoints/anywhere_freight_pricing_top_ports_destination.py
@@ -0,0 +1,120 @@
+"""
+Try me out in your browser:
+
+[](https://mybinder.org/v2/gh/VorTECHsa/python-sdk/master?filepath=docs%2Fexamples%2Ftry_me_out%2Fanywhere_freight_pricing_top_ports_destination.ipynb)
+"""
+from typing import Any, Dict, List, Optional
+
+from vortexasdk.client import default_client, _handle_response
+from vortexasdk.endpoints.endpoints import (
+ ANYWHERE_FREIGHT_PRICING_TOP_PORTS_DESTINATION,
+)
+from vortexasdk.endpoints.anywhere_freight_pricing_result import (
+ AnywhereFreightPricingResult,
+)
+from vortexasdk.endpoints.anywhere_freight_pricing_types import (
+ AfpAvoidZone,
+ AfpProduct,
+ AfpUnit,
+ AfpVesselClass,
+)
+from vortexasdk.logger import get_logger
+from vortexasdk.retry_session import retry_post
+
+logger = get_logger(__name__)
+
+
+class AnywhereFreightPricingTopPortsDestination:
+ """
+ Anywhere Freight Pricing Top Ports Destination endpoint.
+
+ List top destination ports. A top destination port refers to the port
+ with the greatest volume of incoming voyages from vessels in a specified class.
+
+ Please note: you will require a subscription to our Anywhere Freight Pricing
+ module to access this endpoint.
+ """
+
+ def __init__(self) -> None:
+ self._resource = ANYWHERE_FREIGHT_PRICING_TOP_PORTS_DESTINATION
+
+ def search(
+ self,
+ origin_id: str,
+ vessel_class: AfpVesselClass,
+ product: AfpProduct,
+ unit: AfpUnit = "usd_per_tonne",
+ avoid_zone: Optional[List[AfpAvoidZone]] = None,
+ ) -> AnywhereFreightPricingResult:
+ """
+ List top destination ports from a given origin.
+
+ A top destination port refers to the port with the greatest volume of
+ incoming voyages from vessels in a specified class.
+
+ # Arguments
+
+ origin_id: Geographical ID of the origin port.
+
+ vessel_class: The vessel class for the route. Must be one of:
+ `'oil_coastal'`, `'oil_specialised'`, `'oil_handysize_mr1'`,
+ `'oil_handymax_mr2'`, `'oil_panamax_lr1'`, `'oil_aframax_lr2'`,
+ `'oil_suezmax_lr3'`, `'oil_vlcc'`.
+
+ product: The product type. Must be one of: `'clean'`, `'dirty'`, `'crude'`.
+
+ unit: The unit for pricing. Must be one of: `'usd_per_tonne'`, `'usd_per_barrel'`.
+ Defaults to `'usd_per_tonne'`.
+
+ avoid_zone: Routing zones to avoid. Options: `'Panama Canal'`, `'Suez Canal'`.
+
+ # Returns
+ `AnywhereFreightPricingResult`
+
+ # Example
+ _Get top destination ports for clean products from Houston using MR2 vessels._
+
+ ```python
+ >>> from vortexasdk import AnywhereFreightPricingTopPortsDestination
+ >>> result = AnywhereFreightPricingTopPortsDestination().search(
+ ... origin_id="7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030ec6b82f944a73d4da2f",
+ ... vessel_class="oil_handymax_mr2",
+ ... product="clean",
+ ... unit="usd_per_tonne",
+ ... )
+ >>> df = result.to_df()
+
+ ```
+
+ Returns a DataFrame with columns including geography info, rates, lumpsums,
+ and confidence values:
+
+ | | geography_name | date | rate | lumpsum | confidence |
+ |---:|:---------------|:-----------|------:|------------:|-----------:|
+ | 0 | Callao [PE] | 2024-01-01 | 63.55 | 2351511.83 | 2 |
+
+ """
+ logger.info(
+ f"Fetching Anywhere Freight Pricing top destination ports for origin {origin_id}"
+ )
+
+ payload: Dict[str, Any] = {
+ "origin_id": origin_id,
+ "vessel_class": vessel_class,
+ "product": product,
+ "unit": unit,
+ }
+
+ if avoid_zone is not None:
+ payload["avoid_zone"] = avoid_zone
+
+ client = default_client()
+ url = client._create_url(self._resource)
+
+ response = retry_post(url, json=payload)
+
+ data = _handle_response(response)
+ return AnywhereFreightPricingResult(
+ records=data.get("data", []),
+ reference=data.get("metadata", {}),
+ )
diff --git a/vortexasdk/endpoints/anywhere_freight_pricing_top_ports_origin.py b/vortexasdk/endpoints/anywhere_freight_pricing_top_ports_origin.py
new file mode 100644
index 00000000..ae659aa1
--- /dev/null
+++ b/vortexasdk/endpoints/anywhere_freight_pricing_top_ports_origin.py
@@ -0,0 +1,120 @@
+"""
+Try me out in your browser:
+
+[](https://mybinder.org/v2/gh/VorTECHsa/python-sdk/master?filepath=docs%2Fexamples%2Ftry_me_out%2Fanywhere_freight_pricing_top_ports_origin.ipynb)
+"""
+from typing import Any, Dict, List, Optional
+
+from vortexasdk.client import default_client, _handle_response
+from vortexasdk.endpoints.endpoints import (
+ ANYWHERE_FREIGHT_PRICING_TOP_PORTS_ORIGIN,
+)
+from vortexasdk.endpoints.anywhere_freight_pricing_result import (
+ AnywhereFreightPricingResult,
+)
+from vortexasdk.endpoints.anywhere_freight_pricing_types import (
+ AfpAvoidZone,
+ AfpProduct,
+ AfpUnit,
+ AfpVesselClass,
+)
+from vortexasdk.logger import get_logger
+from vortexasdk.retry_session import retry_post
+
+logger = get_logger(__name__)
+
+
+class AnywhereFreightPricingTopPortsOrigin:
+ """
+ Anywhere Freight Pricing Top Ports Origin endpoint.
+
+ List top origin ports. A top origin port refers to the port
+ with the greatest volume of outgoing voyages from vessels in a specified class.
+
+ Please note: you will require a subscription to our Anywhere Freight Pricing
+ module to access this endpoint.
+ """
+
+ def __init__(self) -> None:
+ self._resource = ANYWHERE_FREIGHT_PRICING_TOP_PORTS_ORIGIN
+
+ def search(
+ self,
+ destination_id: str,
+ vessel_class: AfpVesselClass,
+ product: AfpProduct,
+ unit: AfpUnit = "usd_per_tonne",
+ avoid_zone: Optional[List[AfpAvoidZone]] = None,
+ ) -> AnywhereFreightPricingResult:
+ """
+ List top origin ports for a given destination.
+
+ A top origin port refers to the port with the greatest volume of
+ outgoing voyages from vessels in a specified class.
+
+ # Arguments
+
+ destination_id: Geographical ID of the destination port.
+
+ vessel_class: The vessel class for the route. Must be one of:
+ `'oil_coastal'`, `'oil_specialised'`, `'oil_handysize_mr1'`,
+ `'oil_handymax_mr2'`, `'oil_panamax_lr1'`, `'oil_aframax_lr2'`,
+ `'oil_suezmax_lr3'`, `'oil_vlcc'`.
+
+ product: The product type. Must be one of: `'clean'`, `'dirty'`, `'crude'`.
+
+ unit: The unit for pricing. Must be one of: `'usd_per_tonne'`, `'usd_per_barrel'`.
+ Defaults to `'usd_per_tonne'`.
+
+ avoid_zone: Routing zones to avoid. Options: `'Panama Canal'`, `'Suez Canal'`.
+
+ # Returns
+ `AnywhereFreightPricingResult`
+
+ # Example
+ _Get top origin ports for clean products to Rotterdam using MR2 vessels._
+
+ ```python
+ >>> from vortexasdk import AnywhereFreightPricingTopPortsOrigin
+ >>> result = AnywhereFreightPricingTopPortsOrigin().search(
+ ... destination_id="68faf65af1345067f11dc6723b8da32f00e304a6f33c000118fccd81947deb4e",
+ ... vessel_class="oil_handymax_mr2",
+ ... product="clean",
+ ... unit="usd_per_tonne",
+ ... )
+ >>> df = result.to_df()
+
+ ```
+
+ Returns a DataFrame with columns including geography info, rates, lumpsums,
+ and confidence values:
+
+ | | geography_name | date | rate | lumpsum | confidence |
+ |---:|:---------------|:-----------|------:|------------:|-----------:|
+ | 0 | Houston [US] | 2024-01-01 | 63.55 | 2351511.83 | 2 |
+
+ """
+ logger.info(
+ f"Fetching Anywhere Freight Pricing top origin ports for destination {destination_id}"
+ )
+
+ payload: Dict[str, Any] = {
+ "destination_id": destination_id,
+ "vessel_class": vessel_class,
+ "product": product,
+ "unit": unit,
+ }
+
+ if avoid_zone is not None:
+ payload["avoid_zone"] = avoid_zone
+
+ client = default_client()
+ url = client._create_url(self._resource)
+
+ response = retry_post(url, json=payload)
+
+ data = _handle_response(response)
+ return AnywhereFreightPricingResult(
+ records=data.get("data", []),
+ reference=data.get("metadata", {}),
+ )
diff --git a/vortexasdk/endpoints/anywhere_freight_pricing_types.py b/vortexasdk/endpoints/anywhere_freight_pricing_types.py
new file mode 100644
index 00000000..7c182d3e
--- /dev/null
+++ b/vortexasdk/endpoints/anywhere_freight_pricing_types.py
@@ -0,0 +1,50 @@
+"""Type definitions for Anywhere Freight Pricing endpoints."""
+
+from typing import List
+from typing_extensions import Literal, TypedDict
+
+
+# Literal type aliases for AFP parameters
+AfpVesselClass = Literal[
+ "oil_coastal",
+ "oil_specialised",
+ "oil_handysize_mr1",
+ "oil_handymax_mr2",
+ "oil_panamax_lr1",
+ "oil_aframax_lr2",
+ "oil_suezmax_lr3",
+ "oil_vlcc",
+]
+
+AfpProduct = Literal["clean", "dirty", "crude"]
+
+AfpUnit = Literal["usd_per_tonne", "usd_per_barrel"]
+
+AfpFrequency = Literal["day", "week", "doe_week", "month", "quarter", "year"]
+
+AfpAvoidZone = Literal["Panama Canal", "Suez Canal"]
+
+
+class AfpRoute(TypedDict, total=False):
+ """
+ Route specification for AFP POST endpoints.
+
+ Required keys: origin_port, destination_port, product, vessel_class.
+ Optional keys: avoid_zone, suggested_tonnage.
+ """
+
+ origin_port: str
+ destination_port: str
+ product: AfpProduct
+ vessel_class: AfpVesselClass
+ avoid_zone: List[AfpAvoidZone]
+ suggested_tonnage: float
+
+
+class AfpRouteRequired(TypedDict):
+ """Required fields for AfpRoute."""
+
+ origin_port: str
+ destination_port: str
+ product: AfpProduct
+ vessel_class: AfpVesselClass
diff --git a/vortexasdk/endpoints/anywhere_freight_pricing_vessel_classes_details.py b/vortexasdk/endpoints/anywhere_freight_pricing_vessel_classes_details.py
new file mode 100644
index 00000000..fd42172b
--- /dev/null
+++ b/vortexasdk/endpoints/anywhere_freight_pricing_vessel_classes_details.py
@@ -0,0 +1,73 @@
+"""
+Try me out in your browser:
+
+[](https://mybinder.org/v2/gh/VorTECHsa/python-sdk/master?filepath=docs%2Fexamples%2Ftry_me_out%2Fanywhere_freight_pricing_vessel_classes_details.ipynb)
+"""
+from vortexasdk.client import default_client, _handle_response
+from vortexasdk.endpoints.endpoints import (
+ ANYWHERE_FREIGHT_PRICING_VESSEL_CLASSES_DETAILS,
+)
+from vortexasdk.endpoints.anywhere_freight_pricing_result import (
+ AnywhereFreightPricingResult,
+)
+from vortexasdk.logger import get_logger
+from vortexasdk.retry_session import retry_get
+
+logger = get_logger(__name__)
+
+
+class AnywhereFreightPricingVesselClassesDetails:
+ """
+ Anywhere Freight Pricing Vessel Classes Details endpoint.
+
+ Lists all the vessel classes supported for Anywhere Freight Pricing
+ and the tonnages they can carry.
+
+ Please note: you will require a subscription to our Anywhere Freight Pricing
+ module to access this endpoint.
+ """
+
+ def __init__(self) -> None:
+ self._resource = ANYWHERE_FREIGHT_PRICING_VESSEL_CLASSES_DETAILS
+
+ def search(self) -> AnywhereFreightPricingResult:
+ """
+ List vessel classes with tonnages.
+
+ Lists all the vessel classes supported for Anywhere Freight Pricing
+ and the tonnages they can carry.
+
+ # Returns
+ `AnywhereFreightPricingResult`
+
+ # Example
+ _Get all supported vessel classes and their tonnage ranges._
+
+ ```python
+ >>> from vortexasdk import AnywhereFreightPricingVesselClassesDetails
+ >>> result = AnywhereFreightPricingVesselClassesDetails().search()
+ >>> df = result.to_df()
+
+ ```
+
+ Returns a DataFrame with vessel class details:
+
+ | | name | suggested_tonnage | min_tonnage | max_tonnage |
+ |---:|:------------------|------------------:|------------:|------------:|
+ | 0 | oil_handymax_mr2 | 37000.0 | 18000.0 | 54000.0 |
+ | 1 | oil_panamax_lr1 | 60000.0 | 30000.0 | 79000.0 |
+ | 2 | oil_aframax_lr2 | 85000.0 | 42000.0 | 119000.0 |
+
+ """
+ logger.info("Fetching Anywhere Freight Pricing vessel classes details")
+
+ client = default_client()
+ url = client._create_url(self._resource)
+
+ response = retry_get(url)
+
+ data = _handle_response(response)
+ return AnywhereFreightPricingResult(
+ records=data.get("data", []),
+ reference=data.get("metadata", {}),
+ )
diff --git a/vortexasdk/endpoints/endpoints.py b/vortexasdk/endpoints/endpoints.py
index 3a60a1c4..8d125c7b 100644
--- a/vortexasdk/endpoints/endpoints.py
+++ b/vortexasdk/endpoints/endpoints.py
@@ -33,6 +33,25 @@
FREIGHT_PRICING_SEARCH = "/v5/freight-outlook/rates"
FREIGHT_PRICING_TIMESERIES = "/v5/freight-outlook/timeseries"
+ANYWHERE_FREIGHT_PRICING_LATEST_UPDATE = (
+ "/v5/anywhere-freight-pricing/latest-update-timestamp"
+)
+ANYWHERE_FREIGHT_PRICING_PRICE_TIMESERIES = (
+ "/v5/anywhere-freight-pricing/price-timeseries"
+)
+ANYWHERE_FREIGHT_PRICING_PRICE_DETAILS = (
+ "/v5/anywhere-freight-pricing/price/details"
+)
+ANYWHERE_FREIGHT_PRICING_TOP_PORTS_DESTINATION = (
+ "/v5/anywhere-freight-pricing/top-ports/destination"
+)
+ANYWHERE_FREIGHT_PRICING_TOP_PORTS_ORIGIN = (
+ "/v5/anywhere-freight-pricing/top-ports/origin"
+)
+ANYWHERE_FREIGHT_PRICING_VESSEL_CLASSES_DETAILS = (
+ "/v5/anywhere-freight-pricing/vessel-classes-details"
+)
+
VOYAGES_SEARCH_ENRICHED = "/v5/voyages/search-enriched"
VOYAGES_TOP_HITS = "/v5/voyages/top-hits"
VOYAGES_CONGESTION_BREAKDOWN = "/v5/voyages/congestion-breakdown"