diff --git a/django/climate_change_api/indicators/abstract_indicators.py b/django/climate_change_api/indicators/abstract_indicators.py index 62d11f88..66d0fe98 100644 --- a/django/climate_change_api/indicators/abstract_indicators.py +++ b/django/climate_change_api/indicators/abstract_indicators.py @@ -7,12 +7,10 @@ from climate_data.models import ClimateData -from climate_data.filters import ClimateDataFilterSet from .params import IndicatorParams, ThresholdIndicatorParams from .serializers import IndicatorSerializer from .unit_converters import DaysUnitsMixin, TemperatureConverter, PrecipitationConverter -from .query_ranges import (MonthQuerysetGenerator, QuarterQuerysetGenerator, YearQuerysetGenerator, - OffsetYearQuerysetGenerator, CustomQuerysetGenerator) +import queryset_generator class Indicator(object): @@ -108,21 +106,16 @@ def get_queryset(self): by the constructor """ - # Use the ranges to determine how each time aggregation should be keyed - range_config = { - 'monthly': MonthQuerysetGenerator, - 'quarterly': QuarterQuerysetGenerator, - 'yearly': YearQuerysetGenerator, - 'offset_yearly': OffsetYearQuerysetGenerator, - 'custom': CustomQuerysetGenerator - }.get(self.params.time_aggregation.value) + # Get the queryset generator for this indicator's time aggregation + generator = queryset_generator.get(self.params.time_aggregation.value) key_params = {} # The custom range config accepts a user-defined parameter to pick which dates to use if self.params.custom_time_agg.value is not None: key_params['custom_time_agg'] = self.params.custom_time_agg.value - queryset = range_config.create_queryset( + # Use the queryset generator classes to construct the initial base climate data queryset + queryset = generator.create_queryset( years=self.params.years.value, models=self.params.models.value, scenario=self.scenario, diff --git a/django/climate_change_api/indicators/queryset_generator.py b/django/climate_change_api/indicators/queryset_generator.py index 11f81c44..3fd4b509 100644 --- a/django/climate_change_api/indicators/queryset_generator.py +++ b/django/climate_change_api/indicators/queryset_generator.py @@ -7,6 +7,17 @@ from climate_data.filters import ClimateDataFilterSet +def get(time_aggregation): + """ Provide the correct queryset generator class based on indicator time aggregation """ + return { + 'monthly': MonthQuerysetGenerator, + 'quarterly': QuarterQuerysetGenerator, + 'yearly': YearQuerysetGenerator, + 'offset_yearly': OffsetYearQuerysetGenerator, + 'custom': CustomQuerysetGenerator + }.get(time_aggregation) + + class QuerysetGenerator(object): """ Utility class to create querysets for ClimateData for a given time aggregation @@ -16,6 +27,7 @@ class QuerysetGenerator(object): CaseRange = namedtuple('CaseRange', ('key', 'start', 'length')) range_config = None + filterset_kwargs = {} @staticmethod def get_leap_year_sets(): @@ -43,15 +55,16 @@ def create_queryset(cls, scenario, years=None, models=None, key_params=None): data_source__scenario=scenario)) if years is not None or models is not None: - filterset = cls.get_filter_set() - queryset = filterset.filter_years(queryset, years) - queryset = filterset.filter_models(queryset, models) + queryset = cls.apply_filters(queryset, years, models) return queryset @classmethod - def get_filter_set(cls): - return ClimateDataFilterSet() + def apply_filters(cls, queryset, years, models): + filterset = ClimateDataFilterSet(**cls.filterset_kwargs) + queryset = filterset.filter_years(queryset, years) + queryset = filterset.filter_models(queryset, models) + return queryset @classmethod def get_interval_key(cls, index): @@ -201,6 +214,7 @@ class OffsetYearQuerysetGenerator(QuerysetGenerator): # By default place the year divide near the summer solstice to maximize the span that covers # winter custom_offset = 180 + filterset_kwargs = {'year_col': 'offset_year'} @classmethod def make_ranges(cls, label): @@ -220,10 +234,6 @@ def make_ranges(cls, label): @classmethod def create_queryset(cls, *args, **kwargs): queryset = super(OffsetYearQuerysetGenerator, cls).create_queryset(*args, **kwargs) - queryset = queryset.annotate(offset_year=Case( - When(day_of_year__lt=cls.custom_offset, - then=F('data_source__year') - 1), - default=F('data_source__year'))) scenario = kwargs['scenario'] maxYear, minYear = (Scenario.objects @@ -239,5 +249,10 @@ def create_queryset(cls, *args, **kwargs): return queryset @classmethod - def get_filter_set(cls): - return ClimateDataFilterSet(year_col='offset_year') + def apply_filters(cls, queryset, years, models): + queryset = queryset.annotate(offset_year=Case( + When(day_of_year__lt=cls.custom_offset, + then=F('data_source__year') - 1), + default=F('data_source__year'))) + + return super(OffsetYearQuerysetGenerator, cls).apply_filters(queryset, years, models) diff --git a/django/climate_change_api/indicators/tests/test_indicators.py b/django/climate_change_api/indicators/tests/test_indicators.py index fd3f704a..2b074160 100644 --- a/django/climate_change_api/indicators/tests/test_indicators.py +++ b/django/climate_change_api/indicators/tests/test_indicators.py @@ -385,6 +385,27 @@ class YearlyHeatingDegreeDaysTestCase(IndicatorTests, TestCase): 2003: {'avg': 58.5, 'min': 58.5, 'max': 58.5}} +class CrossYearlyHeatingDegreeDaysTestCase(IndicatorTests, TestCase): + indicator_class = indicators.HeatingDegreeDays + indicator_name = 'heating_degree_days' + time_aggregation = 'offset_yearly' + extra_params = { + 'basetemp': '42.5', + 'basetemp_units': 'K' + } + test_indicator_rcp85_equals = {} + test_indicator_rcp45_equals = {'2000-2001': {'avg': 49.5, 'min': 40.5, 'max': 58.5}, + '2001-2002': {'avg': 58.5, 'min': 58.5, 'max': 58.5}, + '2002-2003': {'avg': 58.5, 'min': 58.5, 'max': 58.5}} + # Years are filtered by the starting year, so years=2001,2002 gives data for 2001-2002 + # and 2002-2003 + test_years_filter_equals = {'2001-2002': {'avg': 58.5, 'min': 58.5, 'max': 58.5}, + '2002-2003': {'avg': 58.5, 'min': 58.5, 'max': 58.5}} + test_models_filter_equals = {'2000-2001': {'avg': 58.5, 'min': 58.5, 'max': 58.5}, + '2001-2002': {'avg': 58.5, 'min': 58.5, 'max': 58.5}, + '2002-2003': {'avg': 58.5, 'min': 58.5, 'max': 58.5}} + + class YearlyCoolingDegreeDaysTestCase(IndicatorTests, TestCase): indicator_class = indicators.CoolingDegreeDays indicator_name = 'cooling_degree_days'