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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
vessel_classmax_dwtsuggested_tonnagesuggested_tonnage_overriddenavoid_zonevia_waypointrateslumpsumsconfidencesproductorigin_port.idorigin_port.latorigin_port.londestination_port.iddestination_port.latdestination_port.lon
0oil_aframax_lr2119999.085000.0FalseNoneNone[{'date': '2024-01-01', 'breakdown': [{'type':...[{'date': '2024-01-01', 'breakdown': [{'type':...[{'date': '2024-01-01', 'value': 2}, {'date': ...crude7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030e...29.71926-95.1473368faf65af1345067f11dc6723b8da32f00e304a6f33c00...51.909244.27941
\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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
vessel_classmax_dwtsuggested_tonnagesuggested_tonnage_overriddenavoid_zonevia_waypointrateslumpsumsconfidencesproductorigin_port.idorigin_port.latorigin_port.londestination_port.iddestination_port.latdestination_port.lon
0oil_aframax_lr2119999.085000.0FalseNoneNone[{'date': '2024-01-01', 'breakdown': [{'type':...[{'date': '2024-01-01', 'breakdown': [{'type':...[{'date': '2024-01-01', 'value': 2}, {'date': ...crude7f314ba0a498c36359b1c88781e94a73e19dcc9bbb030e...29.71926-95.1473368faf65af1345067f11dc6723b8da32f00e304a6f33c00...51.909244.27941
1oil_handymax_mr254999.037000.0FalseNoneNone[{'date': '2024-01-01', 'breakdown': [{'type':...[{'date': '2024-01-01', 'breakdown': [{'type':...[{'date': '2024-01-01', 'value': 3}, {'date': ...clean68faf65af1345067f11dc6723b8da32f00e304a6f33c00...51.909244.27941ea4921c8ad4fddb5fe3e7a4f834c1aa5863e43283c73da...40.68878-74.05066
\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: + +[![Binder](https://img.shields.io/badge/try%20me%20out-launch%20notebook-579ACA.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAABZCAMAAABi1XidAAAB8lBMVEX///9XmsrmZYH1olJXmsr1olJXmsrmZYH1olJXmsr1olJXmsrmZYH1olL1olJXmsr1olJXmsrmZYH1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olJXmsrmZYH1olL1olL0nFf1olJXmsrmZYH1olJXmsq8dZb1olJXmsrmZYH1olJXmspXmspXmsr1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olLeaIVXmsrmZYH1olL1olL1olJXmsrmZYH1olLna31Xmsr1olJXmsr1olJXmsrmZYH1olLqoVr1olJXmsr1olJXmsrmZYH1olL1olKkfaPobXvviGabgadXmsqThKuofKHmZ4Dobnr1olJXmsr1olJXmspXmsr1olJXmsrfZ4TuhWn1olL1olJXmsqBi7X1olJXmspZmslbmMhbmsdemsVfl8ZgmsNim8Jpk8F0m7R4m7F5nLB6jbh7jbiDirOEibOGnKaMhq+PnaCVg6qWg6qegKaff6WhnpKofKGtnomxeZy3noG6dZi+n3vCcpPDcpPGn3bLb4/Mb47UbIrVa4rYoGjdaIbeaIXhoWHmZYHobXvpcHjqdHXreHLroVrsfG/uhGnuh2bwj2Hxk17yl1vzmljzm1j0nlX1olL3AJXWAAAAbXRSTlMAEBAQHx8gICAuLjAwMDw9PUBAQEpQUFBXV1hgYGBkcHBwcXl8gICAgoiIkJCQlJicnJ2goKCmqK+wsLC4usDAwMjP0teleN20NbsvaOsY2+3LL9/Tz1bC0dLQ1dXZ2Nrf4+Lk5urq7/P3+fn4+Pz9/f7+/gB+Q1KaQJdScAAAAAElFTkSuQmCC)](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: + +[![Binder](https://img.shields.io/badge/try%20me%20out-launch%20notebook-579ACA.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAABZCAMAAABi1XidAAAB8lBMVEX///9XmsrmZYH1olJXmsr1olJXmsrmZYH1olJXmsr1olJXmsrmZYH1olL1olJXmsr1olJXmsrmZYH1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olJXmsrmZYH1olL1olL0nFf1olJXmsrmZYH1olJXmsq8dZb1olJXmsrmZYH1olJXmspXmspXmsr1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olLeaIVXmsrmZYH1olL1olL1olJXmsrmZYH1olLna31Xmsr1olJXmsr1olJXmsrmZYH1olLqoVr1olJXmsr1olJXmsrmZYH1olL1olKkfaPobXvviGabgadXmsqThKuofKHmZ4Dobnr1olJXmsr1olJXmspXmsr1olJXmsrfZ4TuhWn1olL1olJXmsqBi7X1olJXmspZmslbmMhbmsdemsVfl8ZgmsNim8Jpk8F0m7R4m7F5nLB6jbh7jbiDirOEibOGnKaMhq+PnaCVg6qWg6qegKaff6WhnpKofKGtnomxeZy3noG6dZi+n3vCcpPDcpPGn3bLb4/Mb47UbIrVa4rYoGjdaIbeaIXhoWHmZYHobXvpcHjqdHXreHLroVrsfG/uhGnuh2bwj2Hxk17yl1vzmljzm1j0nlX1olL3AJXWAAAAbXRSTlMAEBAQHx8gICAuLjAwMDw9PUBAQEpQUFBXV1hgYGBkcHBwcXl8gICAgoiIkJCQlJicnJ2goKCmqK+wsLC4usDAwMjP0teleN20NbsvaOsY2+3LL9/Tz1bC0dLQ1dXZ2Nrf4+Lk5urq7/P3+fn4+Pz9/f7+/gB+Q1KaQJdScAAAAAElFTkSuQmCC)](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: + +[![Binder](https://img.shields.io/badge/try%20me%20out-launch%20notebook-579ACA.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAABZCAMAAABi1XidAAAB8lBMVEX///9XmsrmZYH1olJXmsr1olJXmsrmZYH1olJXmsr1olJXmsrmZYH1olL1olJXmsr1olJXmsrmZYH1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olJXmsrmZYH1olL1olL0nFf1olJXmsrmZYH1olJXmsq8dZb1olJXmsrmZYH1olJXmspXmspXmsr1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olLeaIVXmsrmZYH1olL1olL1olJXmsrmZYH1olLna31Xmsr1olJXmsr1olJXmsrmZYH1olLqoVr1olJXmsr1olJXmsrmZYH1olL1olKkfaPobXvviGabgadXmsqThKuofKHmZ4Dobnr1olJXmsr1olJXmspXmsr1olJXmsrfZ4TuhWn1olL1olJXmsqBi7X1olJXmspZmslbmMhbmsdemsVfl8ZgmsNim8Jpk8F0m7R4m7F5nLB6jbh7jbiDirOEibOGnKaMhq+PnaCVg6qWg6qegKaff6WhnpKofKGtnomxeZy3noG6dZi+n3vCcpPDcpPGn3bLb4/Mb47UbIrVa4rYoGjdaIbeaIXhoWHmZYHobXvpcHjqdHXreHLroVrsfG/uhGnuh2bwj2Hxk17yl1vzmljzm1j0nlX1olL3AJXWAAAAbXRSTlMAEBAQHx8gICAuLjAwMDw9PUBAQEpQUFBXV1hgYGBkcHBwcXl8gICAgoiIkJCQlJicnJ2goKCmqK+wsLC4usDAwMjP0teleN20NbsvaOsY2+3LL9/Tz1bC0dLQ1dXZ2Nrf4+Lk5urq7/P3+fn4+Pz9/f7+/gB+Q1KaQJdScAAAAAElFTkSuQmCC)](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: + +[![Binder](https://img.shields.io/badge/try%20me%20out-launch%20notebook-579ACA.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAABZCAMAAABi1XidAAAB8lBMVEX///9XmsrmZYH1olJXmsr1olJXmsrmZYH1olJXmsr1olJXmsrmZYH1olL1olJXmsr1olJXmsrmZYH1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olJXmsrmZYH1olL1olL0nFf1olJXmsrmZYH1olJXmsq8dZb1olJXmsrmZYH1olJXmspXmspXmsr1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olLeaIVXmsrmZYH1olL1olL1olJXmsrmZYH1olLna31Xmsr1olJXmsr1olJXmsrmZYH1olLqoVr1olJXmsr1olJXmsrmZYH1olL1olKkfaPobXvviGabgadXmsqThKuofKHmZ4Dobnr1olJXmsr1olJXmspXmsr1olJXmsrfZ4TuhWn1olL1olJXmsqBi7X1olJXmspZmslbmMhbmsdemsVfl8ZgmsNim8Jpk8F0m7R4m7F5nLB6jbh7jbiDirOEibOGnKaMhq+PnaCVg6qWg6qegKaff6WhnpKofKGtnomxeZy3noG6dZi+n3vCcpPDcpPGn3bLb4/Mb47UbIrVa4rYoGjdaIbeaIXhoWHmZYHobXvpcHjqdHXreHLroVrsfG/uhGnuh2bwj2Hxk17yl1vzmljzm1j0nlX1olL3AJXWAAAAbXRSTlMAEBAQHx8gICAuLjAwMDw9PUBAQEpQUFBXV1hgYGBkcHBwcXl8gICAgoiIkJCQlJicnJ2goKCmqK+wsLC4usDAwMjP0teleN20NbsvaOsY2+3LL9/Tz1bC0dLQ1dXZ2Nrf4+Lk5urq7/P3+fn4+Pz9/f7+/gB+Q1KaQJdScAAAAAElFTkSuQmCC)](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: + +[![Binder](https://img.shields.io/badge/try%20me%20out-launch%20notebook-579ACA.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAABZCAMAAABi1XidAAAB8lBMVEX///9XmsrmZYH1olJXmsr1olJXmsrmZYH1olJXmsr1olJXmsrmZYH1olL1olJXmsr1olJXmsrmZYH1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olJXmsrmZYH1olL1olL0nFf1olJXmsrmZYH1olJXmsq8dZb1olJXmsrmZYH1olJXmspXmspXmsr1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olLeaIVXmsrmZYH1olL1olL1olJXmsrmZYH1olLna31Xmsr1olJXmsr1olJXmsrmZYH1olLqoVr1olJXmsr1olJXmsrmZYH1olL1olKkfaPobXvviGabgadXmsqThKuofKHmZ4Dobnr1olJXmsr1olJXmspXmsr1olJXmsrfZ4TuhWn1olL1olJXmsqBi7X1olJXmspZmslbmMhbmsdemsVfl8ZgmsNim8Jpk8F0m7R4m7F5nLB6jbh7jbiDirOEibOGnKaMhq+PnaCVg6qWg6qegKaff6WhnpKofKGtnomxeZy3noG6dZi+n3vCcpPDcpPGn3bLb4/Mb47UbIrVa4rYoGjdaIbeaIXhoWHmZYHobXvpcHjqdHXreHLroVrsfG/uhGnuh2bwj2Hxk17yl1vzmljzm1j0nlX1olL3AJXWAAAAbXRSTlMAEBAQHx8gICAuLjAwMDw9PUBAQEpQUFBXV1hgYGBkcHBwcXl8gICAgoiIkJCQlJicnJ2goKCmqK+wsLC4usDAwMjP0teleN20NbsvaOsY2+3LL9/Tz1bC0dLQ1dXZ2Nrf4+Lk5urq7/P3+fn4+Pz9/f7+/gB+Q1KaQJdScAAAAAElFTkSuQmCC)](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: + +[![Binder](https://img.shields.io/badge/try%20me%20out-launch%20notebook-579ACA.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAABZCAMAAABi1XidAAAB8lBMVEX///9XmsrmZYH1olJXmsr1olJXmsrmZYH1olJXmsr1olJXmsrmZYH1olL1olJXmsr1olJXmsrmZYH1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olJXmsrmZYH1olL1olL0nFf1olJXmsrmZYH1olJXmsq8dZb1olJXmsrmZYH1olJXmspXmspXmsr1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olLeaIVXmsrmZYH1olL1olL1olJXmsrmZYH1olLna31Xmsr1olJXmsr1olJXmsrmZYH1olLqoVr1olJXmsr1olJXmsrmZYH1olL1olKkfaPobXvviGabgadXmsqThKuofKHmZ4Dobnr1olJXmsr1olJXmspXmsr1olJXmsrfZ4TuhWn1olL1olJXmsqBi7X1olJXmspZmslbmMhbmsdemsVfl8ZgmsNim8Jpk8F0m7R4m7F5nLB6jbh7jbiDirOEibOGnKaMhq+PnaCVg6qWg6qegKaff6WhnpKofKGtnomxeZy3noG6dZi+n3vCcpPDcpPGn3bLb4/Mb47UbIrVa4rYoGjdaIbeaIXhoWHmZYHobXvpcHjqdHXreHLroVrsfG/uhGnuh2bwj2Hxk17yl1vzmljzm1j0nlX1olL3AJXWAAAAbXRSTlMAEBAQHx8gICAuLjAwMDw9PUBAQEpQUFBXV1hgYGBkcHBwcXl8gICAgoiIkJCQlJicnJ2goKCmqK+wsLC4usDAwMjP0teleN20NbsvaOsY2+3LL9/Tz1bC0dLQ1dXZ2Nrf4+Lk5urq7/P3+fn4+Pz9/f7+/gB+Q1KaQJdScAAAAAElFTkSuQmCC)](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: + +[![Binder](https://img.shields.io/badge/try%20me%20out-launch%20notebook-579ACA.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAABZCAMAAABi1XidAAAB8lBMVEX///9XmsrmZYH1olJXmsr1olJXmsrmZYH1olJXmsr1olJXmsrmZYH1olL1olJXmsr1olJXmsrmZYH1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olJXmsrmZYH1olL1olL0nFf1olJXmsrmZYH1olJXmsq8dZb1olJXmsrmZYH1olJXmspXmspXmsr1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olLeaIVXmsrmZYH1olL1olL1olJXmsrmZYH1olLna31Xmsr1olJXmsr1olJXmsrmZYH1olLqoVr1olJXmsr1olJXmsrmZYH1olL1olKkfaPobXvviGabgadXmsqThKuofKHmZ4Dobnr1olJXmsr1olJXmspXmsr1olJXmsrfZ4TuhWn1olL1olJXmsqBi7X1olJXmspZmslbmMhbmsdemsVfl8ZgmsNim8Jpk8F0m7R4m7F5nLB6jbh7jbiDirOEibOGnKaMhq+PnaCVg6qWg6qegKaff6WhnpKofKGtnomxeZy3noG6dZi+n3vCcpPDcpPGn3bLb4/Mb47UbIrVa4rYoGjdaIbeaIXhoWHmZYHobXvpcHjqdHXreHLroVrsfG/uhGnuh2bwj2Hxk17yl1vzmljzm1j0nlX1olL3AJXWAAAAbXRSTlMAEBAQHx8gICAuLjAwMDw9PUBAQEpQUFBXV1hgYGBkcHBwcXl8gICAgoiIkJCQlJicnJ2goKCmqK+wsLC4usDAwMjP0teleN20NbsvaOsY2+3LL9/Tz1bC0dLQ1dXZ2Nrf4+Lk5urq7/P3+fn4+Pz9/f7+/gB+Q1KaQJdScAAAAAElFTkSuQmCC)](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"