From 130bf0e1c3d05e0e2e9c1505daaf42cae0b32549 Mon Sep 17 00:00:00 2001 From: Christopher Burgess Date: Mon, 9 Mar 2020 17:32:07 +0000 Subject: [PATCH 1/3] feat: Add timeseries activity min and max time span --- tests/endpoints/test_cargo_movements_real.py | 16 ++++++++++++++++ vortexasdk/endpoints/cargo_movements.py | 14 ++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/tests/endpoints/test_cargo_movements_real.py b/tests/endpoints/test_cargo_movements_real.py index 7b01864d..aefd9ea9 100644 --- a/tests/endpoints/test_cargo_movements_real.py +++ b/tests/endpoints/test_cargo_movements_real.py @@ -81,6 +81,22 @@ def test_search_single_filter_origin_name(self): assert len(df) == 2 + def test_search_filters_on_timeseries_max_activity(self): + df = ( + CargoMovements() + .search( + filter_activity="storing_state", + filter_time_min=datetime(2019, 8, 29), + filter_time_max=datetime(2019, 8, 29, 0, 10), + timeseries_activity_time_span_min=1000 * 60 * 60 * 24 * 14, + timeseries_activity_time_span_max=1000 * 60 * 60 * 24 * 60 + ) + .to_df() + .head(2) + ) + + assert len(df) == 2 + def test_search_single_filter_owner_name(self): df = ( CargoMovements() diff --git a/vortexasdk/endpoints/cargo_movements.py b/vortexasdk/endpoints/cargo_movements.py index 30bc073b..0190bd0e 100644 --- a/vortexasdk/endpoints/cargo_movements.py +++ b/vortexasdk/endpoints/cargo_movements.py @@ -41,6 +41,8 @@ def search( filter_ship_to_ship_locations: Union[ID, List[ID]] = None, filter_waypoints: Union[ID, List[ID]] = None, disable_geographic_exclusion_rules: bool = None, + timeseries_activity_time_span_min: int = None, + timeseries_activity_time_span_max: int = None ) -> CargoMovementsResult: """ @@ -79,6 +81,16 @@ def search( disable_geographic_exclusion_rules: This controls a popular industry term "intra-movements" and determines the filter behaviour for cargo leaving then entering the same geographic area. + timeseries_activity_time_span_min: The minimum amount of time in milliseconds accounted for in a time series + activity. Can be used to request short-term floating storage. For example, to only return floating storage + movements that occured for _more_ than 14 days enter `timeseries_activity_time_span_min=1000 * 60 * 60 * 24 * 14` in conjunction + with `filter_activity='storing_state'`. + + timeseries_activity_time_span_max: The maximum amount of time in milliseconds accounted for in a time series + activity. Can be used to request short-term floating storage. For example, to only return floating storage + movements that occured for _less_ than 14 days enter `timeseries_activity_time_span_max=1000 * 60 * 60 * 24 * 14` in conjunction + with `filter_activity='storing_state'`. + # Returns `CargoMovementsResult`, containing all the cargo movements matching the given search terms. @@ -164,6 +176,8 @@ def search( ), "filter_waypoints": convert_to_list(filter_waypoints), "disable_geographic_exclusion_rules": disable_geographic_exclusion_rules, + "timeseries_activity_time_span_min": timeseries_activity_time_span_min, + "timeseries_activity_time_span_max": timeseries_activity_time_span_max } return CargoMovementsResult(super().search(**params)) From 4448ba8669e2edd1185b008488113a848607eaa7 Mon Sep 17 00:00:00 2001 From: Christopher Burgess Date: Mon, 9 Mar 2020 17:49:33 +0000 Subject: [PATCH 2/3] feat: Add floating storage example --- .../6_medium_sour_floating_storage.py | 51 +++++++++++++++++++ pydocmd.yml | 1 + tests/endpoints/test_cargo_movements_real.py | 8 +-- vortexasdk/endpoints/cargo_movements.py | 16 +++--- vortexasdk/endpoints/cargo_timeseries.py | 17 +++++++ vortexasdk/retry_session.py | 8 +-- 6 files changed, 86 insertions(+), 15 deletions(-) create mode 100644 docs/examples/6_medium_sour_floating_storage.py diff --git a/docs/examples/6_medium_sour_floating_storage.py b/docs/examples/6_medium_sour_floating_storage.py new file mode 100644 index 00000000..784e1b3b --- /dev/null +++ b/docs/examples/6_medium_sour_floating_storage.py @@ -0,0 +1,51 @@ +""" +Let's see how much Medium-Sour Crude is in long term floating storage. + +The below script returns: + +| | key | value | count | +|----:|:-------------------------|---------:|--------:| +| 0 | 2019-01-01T00:00:00.000Z | 7381 | 9 | +| 1 | 2019-01-02T00:00:00.000Z | 8127 | 23 | +| 2 | 2019-01-03T00:00:00.000Z | 2333 | 32 | +| 3 | 2019-01-04T00:00:00.000Z | 447759 | 43 | +| 4 | 2019-01-05T00:00:00.000Z | 7777144 | 4 | +... + +""" +from datetime import datetime + +from docs.utils import to_markdown +from vortexasdk import CargoTimeSeries, Geographies, Products + +if __name__ == "__main__": + # Find Medium Sour ID + medium_sour = [ + p.id + for p in Products().search(term="Medium-Sour").to_list() + if p.name == "Medium-Sour" + ] + # Check we've only got one ID + assert len(medium_sour) == 1 + + # Query API + search_result = CargoTimeSeries().search( + # We're looking at daily storage levels + timeseries_frequency="day", + # We want 'b' for barrels here + timeseries_unit="b", + # We're only interested in storage of Medium-Sour Crude + filter_products=medium_sour, + # We're only included in cargo's that were in floating storage + filter_activity="storing_state", + # We're only interested in floating storage that lasted longer than 24 days + timeseries_activity_time_span_min=1000 * 60 * 60 * 24, + # Let's limit the search to 2019 storage events + filter_time_min=datetime(2019, 1, 1), + filter_time_max=datetime(2019, 12, 31), + ) + + # Convert search result to dataframe + df = search_result.to_df() + + print(to_markdown(df.head())) diff --git a/pydocmd.yml b/pydocmd.yml index 44f4434a..b32062a7 100644 --- a/pydocmd.yml +++ b/pydocmd.yml @@ -64,6 +64,7 @@ pages: 3 Crude movements from Saudi Arabia to India: examples/3_crude_from_saudi_arabia_to_india.md 4 Ballast Movements: examples/4_ballast_movements.md 5 Chinese Daily Crude Imports: examples/5_chinese_daily_imports.md + 6 Floating Storage Medium Sour Crude: examples/6_medium_sour_floating_storage.md theme: name: readthedocs collapse_navigation: true diff --git a/tests/endpoints/test_cargo_movements_real.py b/tests/endpoints/test_cargo_movements_real.py index aefd9ea9..9d56a9c1 100644 --- a/tests/endpoints/test_cargo_movements_real.py +++ b/tests/endpoints/test_cargo_movements_real.py @@ -84,15 +84,15 @@ def test_search_single_filter_origin_name(self): def test_search_filters_on_timeseries_max_activity(self): df = ( CargoMovements() - .search( + .search( filter_activity="storing_state", filter_time_min=datetime(2019, 8, 29), filter_time_max=datetime(2019, 8, 29, 0, 10), timeseries_activity_time_span_min=1000 * 60 * 60 * 24 * 14, - timeseries_activity_time_span_max=1000 * 60 * 60 * 24 * 60 + timeseries_activity_time_span_max=1000 * 60 * 60 * 24 * 60, ) - .to_df() - .head(2) + .to_df() + .head(2) ) assert len(df) == 2 diff --git a/vortexasdk/endpoints/cargo_movements.py b/vortexasdk/endpoints/cargo_movements.py index 0190bd0e..18f53f2b 100644 --- a/vortexasdk/endpoints/cargo_movements.py +++ b/vortexasdk/endpoints/cargo_movements.py @@ -42,7 +42,7 @@ def search( filter_waypoints: Union[ID, List[ID]] = None, disable_geographic_exclusion_rules: bool = None, timeseries_activity_time_span_min: int = None, - timeseries_activity_time_span_max: int = None + timeseries_activity_time_span_max: int = None, ) -> CargoMovementsResult: """ @@ -82,14 +82,16 @@ def search( the filter behaviour for cargo leaving then entering the same geographic area. timeseries_activity_time_span_min: The minimum amount of time in milliseconds accounted for in a time series - activity. Can be used to request short-term floating storage. For example, to only return floating storage - movements that occured for _more_ than 14 days enter `timeseries_activity_time_span_min=1000 * 60 * 60 * 24 * 14` in conjunction - with `filter_activity='storing_state'`. + activity. Can be used to request long-term floating storage. For example, to only return floating storage + movements that occured for _more_ than 14 days enter + `timeseries_activity_time_span_min=1000 * 60 * 60 * 24 * 14` in conjunction with + `filter_activity='storing_state'`. timeseries_activity_time_span_max: The maximum amount of time in milliseconds accounted for in a time series activity. Can be used to request short-term floating storage. For example, to only return floating storage - movements that occured for _less_ than 14 days enter `timeseries_activity_time_span_max=1000 * 60 * 60 * 24 * 14` in conjunction - with `filter_activity='storing_state'`. + movements that occured for _less_ than 14 days enter + `timeseries_activity_time_span_max=1000 * 60 * 60 * 24 * 14` + in conjunction with `filter_activity='storing_state'`. # Returns `CargoMovementsResult`, containing all the cargo movements matching the given search terms. @@ -177,7 +179,7 @@ def search( "filter_waypoints": convert_to_list(filter_waypoints), "disable_geographic_exclusion_rules": disable_geographic_exclusion_rules, "timeseries_activity_time_span_min": timeseries_activity_time_span_min, - "timeseries_activity_time_span_max": timeseries_activity_time_span_max + "timeseries_activity_time_span_max": timeseries_activity_time_span_max, } return CargoMovementsResult(super().search(**params)) diff --git a/vortexasdk/endpoints/cargo_timeseries.py b/vortexasdk/endpoints/cargo_timeseries.py index 36809f57..8f23dbbf 100644 --- a/vortexasdk/endpoints/cargo_timeseries.py +++ b/vortexasdk/endpoints/cargo_timeseries.py @@ -32,6 +32,8 @@ def search( filter_ship_to_ship_locations: Union[str, List[str]] = None, filter_waypoints: Union[str, List[str]] = None, disable_geographic_exclusion_rules: bool = None, + timeseries_activity_time_span_min: int = None, + timeseries_activity_time_span_max: int = None, ) -> TimeSeriesResult: """ @@ -41,6 +43,7 @@ def search( * _How many Crude/Condensate barrels have been imported into China each day over the last year?_ * _How many tonnes of Fuel Oil has company X exported from the United States each week over the last 2 years?_ + * _How have long-term Medium-Sour floating storage levels changed over time?_ # Arguments filter_activity: Movement activity on which to base the time filter. Must be one of ['loading_state', @@ -85,6 +88,18 @@ def search( disable_geographic_exclusion_rules: This controls a popular industry term "intra-movements" and determines the filter behaviour for cargo leaving then entering the same geographic area. + timeseries_activity_time_span_min: The minimum amount of time in milliseconds accounted for in a time series + activity. Can be used to request long-term floating storage. For example, to only return floating storage + movements that occured for _more_ than 14 days enter + `timeseries_activity_time_span_min=1000 * 60 * 60 * 24 * 14` in conjunction with + `filter_activity='storing_state'`. + + timeseries_activity_time_span_max: The maximum amount of time in milliseconds accounted for in a time series + activity. Can be used to request short-term floating storage. For example, to only return floating storage + movements that occured for _less_ than 14 days enter + `timeseries_activity_time_span_max=1000 * 60 * 60 * 24 * 14` + in conjunction with `filter_activity='storing_state'`. + # Returns `TimeSeriesResult` @@ -149,6 +164,8 @@ def search( ), "filter_waypoints": convert_to_list(filter_waypoints), "disable_geographic_exclusion_rules": disable_geographic_exclusion_rules, + "timeseries_activity_time_span_min": timeseries_activity_time_span_min, + "timeseries_activity_time_span_max": timeseries_activity_time_span_max, } return TimeSeriesResult(super().search(**params)) diff --git a/vortexasdk/retry_session.py b/vortexasdk/retry_session.py index 38c87e36..30426d81 100644 --- a/vortexasdk/retry_session.py +++ b/vortexasdk/retry_session.py @@ -7,10 +7,10 @@ # Inspired by https://www.peterbe.com/plog/best-practice-with-retries-with-requests def _requests_retry_session( - retries=6, - backoff_factor=1, - status_forcelist=(500, 502, 504), - session=None, + retries=6, + backoff_factor=1, + status_forcelist=(500, 502, 504), + session=None, ) -> Session: """Instantiate a session with Retry backoff.""" session = session or Session() From 39a3e9b9f5378ac456548e4a8e79188ae2d13edd Mon Sep 17 00:00:00 2001 From: Christopher Burgess Date: Mon, 9 Mar 2020 17:55:23 +0000 Subject: [PATCH 3/3] test: Test that CargoTimeSeries can filter on activity timespan --- tests/endpoints/test_cargo_time_series_real.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/endpoints/test_cargo_time_series_real.py b/tests/endpoints/test_cargo_time_series_real.py index ea3f2519..551d0d5d 100644 --- a/tests/endpoints/test_cargo_time_series_real.py +++ b/tests/endpoints/test_cargo_time_series_real.py @@ -119,3 +119,19 @@ def test_filter_geographies_and_products(self): ) print(rotterdam_crude_timeseries.head()) + + def test_search_filters_on_timeseries_max_activity(self): + df = ( + CargoTimeSeries() + .search( + filter_activity="storing_state", + filter_time_min=datetime(2019, 8, 1), + filter_time_max=datetime(2019, 8, 31), + timeseries_activity_time_span_min=1000 * 60 * 60 * 24 * 14, + timeseries_activity_time_span_max=1000 * 60 * 60 * 24 * 60, + ) + .to_df() + .head(2) + ) + + assert len(df) == 2