88from django .utils import timezone
99from dateutil .relativedelta import relativedelta
1010from django .db .models import ExpressionWrapper , F , FloatField , Max , Min , Sum , Avg , Q
11- from django .db .models .functions import Cast , TruncDate
11+ from django .db .models .functions import Cast , TruncDay , TruncHour , TruncMinute , TruncMonth
1212from rest_framework import mixins , pagination , viewsets
1313
14+ from django .db import connection
15+
1416from ..models import SensorDataStat , LastActiveNodes , City , Node
15- from .serializers import SensorDataStatSerializer , CitySerializer
17+ from .serializers import RawSensorDataStatSerializer , CitySerializer
1618
1719from feinstaub .sensors .views import StandardResultsSetPagination
1820
@@ -76,7 +78,8 @@ def get_paginated_response(self, data_stats):
7678 results [city_slug ][value_type ] = [] if from_date else {}
7779
7880 values = results [city_slug ][value_type ]
79- include_result = getattr (values , "append" if from_date else "update" )
81+ include_result = getattr (
82+ values , "append" if from_date else "update" )
8083 include_result (
8184 {
8285 "average" : data_stat ["average" ],
@@ -99,7 +102,7 @@ def get_paginated_response(self, data_stats):
99102
100103class SensorDataStatView (mixins .ListModelMixin , viewsets .GenericViewSet ):
101104 queryset = SensorDataStat .objects .none ()
102- serializer_class = SensorDataStatSerializer
105+ serializer_class = RawSensorDataStatSerializer
103106 pagination_class = CustomPagination
104107
105108 @method_decorator (cache_page (3600 ))
@@ -112,60 +115,31 @@ def get_queryset(self):
112115 city_slugs = self .request .query_params .get ("city" , None )
113116 from_date = self .request .query_params .get ("from" , None )
114117 to_date = self .request .query_params .get ("to" , None )
118+ avg = self .request .query_params .get ("avg" , 'day' )
115119
116120 if to_date and not from_date :
117- raise ValidationError ({"from" : "Must be provide along with to query" })
121+ raise ValidationError (
122+ {"from" : "Must be provide along with to query" })
118123 if from_date :
119- validate_date (from_date , {"from" : "Must be a date in the format Y-m-d." })
124+ validate_date (
125+ from_date , {"from" : "Must be a date in the format Y-m-d." })
120126 if to_date :
121- validate_date (to_date , {"to" : "Must be a date in the format Y-m-d." })
127+ validate_date (
128+ to_date , {"to" : "Must be a date in the format Y-m-d." })
122129
123- value_type_to_filter = self .request .query_params .get ("value_type" , None )
130+ value_type_to_filter = self .request .query_params .get (
131+ "value_type" , None )
124132
125133 filter_value_types = value_types [sensor_type ]
126134 if value_type_to_filter :
127- filter_value_types = set (value_type_to_filter .upper ().split ("," )) & set (
135+ filter_value_types = "," . join ( set (value_type_to_filter .upper ().split ("," )) & set (
128136 [x .upper () for x in value_types [sensor_type ]]
129- )
137+ ))
130138
131139 if not from_date and not to_date :
132- return self ._retrieve_past_24hrs (city_slugs , filter_value_types )
133-
134- return self ._retrieve_range (from_date , to_date , city_slugs , filter_value_types )
135-
136- @staticmethod
137- def _retrieve_past_24hrs (city_slugs , filter_value_types ):
138- to_date = timezone .now ().replace (minute = 0 , second = 0 , microsecond = 0 )
139- from_date = to_date - datetime .timedelta (hours = 24 )
140-
141- queryset = SensorDataStat .objects .filter (
142- value_type__in = filter_value_types ,
143- timestamp__gte = from_date ,
144- timestamp__lte = to_date ,
145- )
146-
147- if city_slugs :
148- queryset = queryset .filter (city_slug__in = city_slugs .split ("," ))
149-
150- return (
151- queryset .order_by ()
152- .values ("value_type" , "city_slug" )
153- .annotate (
154- start_datetime = Min ("timestamp" ),
155- end_datetime = Max ("timestamp" ),
156- average = ExpressionWrapper (
157- Sum (F ("average" ) * F ("sample_size" )) / Sum ("sample_size" ),
158- output_field = FloatField (),
159- ),
160- minimum = Min ("minimum" ),
161- maximum = Max ("maximum" ),
162- )
163- .order_by ("city_slug" )
164- )
165-
166- @staticmethod
167- def _retrieve_range (from_date , to_date , city_slugs , filter_value_types ):
168- if not to_date :
140+ to_date = timezone .now ().replace (minute = 0 , second = 0 , microsecond = 0 )
141+ from_date = to_date - datetime .timedelta (hours = 24 )
142+ elif not to_date :
169143 from_date = beginning_of_day (from_date )
170144 # Get data from_date until the end
171145 # of day yesterday which is the beginning of today
@@ -174,31 +148,36 @@ def _retrieve_range(from_date, to_date, city_slugs, filter_value_types):
174148 from_date = beginning_of_day (from_date )
175149 to_date = end_of_day (to_date )
176150
177- queryset = SensorDataStat .objects .filter (
178- value_type__in = filter_value_types ,
179- timestamp__gte = from_date ,
180- timestamp__lt = to_date ,
181- )
182-
183- if city_slugs :
184- queryset = queryset .filter (city_slug__in = city_slugs .split ("," ))
185-
186- return (
187- queryset .annotate (date = TruncDate ("timestamp" ))
188- .values ("date" , "value_type" )
189- .annotate (
190- city_slug = F ("city_slug" ),
191- start_datetime = Min ("timestamp" ),
192- end_datetime = Max ("timestamp" ),
193- average = ExpressionWrapper (
194- Sum (F ("average" ) * F ("sample_size" )) / Sum ("sample_size" ),
195- output_field = FloatField (),
196- ),
197- minimum = Min ("minimum" ),
198- maximum = Max ("maximum" ),
199- )
200- .order_by ("-date" )
201- )
151+ with connection .cursor () as cursor :
152+ cursor .execute (
153+ """
154+ SELECT
155+ sl.city as city_slug,
156+ min(sd."timestamp") as start_datetime,
157+ max(sd."timestamp") as end_datetime,
158+ sum(CAST("value" as float)) / COUNT(*) AS average,
159+ min(CAST("value" as float)) as minimum,
160+ max(CAST("value" as float)) as maximum,
161+ v.value_type
162+ FROM
163+ sensors_sensordatavalue v
164+ INNER JOIN sensors_sensordata sd ON sd.id = sensordata_id
165+ INNER JOIN sensors_sensorlocation sl ON sl.id = location_id
166+ WHERE
167+ v.value_type IN (%s)
168+ """
169+ +
170+ ("AND sl.city IN (%s)" if city_slugs else "" )
171+ +
172+ """
173+ AND sd."timestamp" >= TIMESTAMP %s
174+ AND sd."timestamp" <= TIMESTAMP %s
175+ GROUP BY
176+ DATE_TRUNC(%s, sd."timestamp"),
177+ v.value_type,
178+ sl.city
179+ """ , [filter_value_types , city_slugs , from_date , to_date , avg ] if city_slugs else [filter_value_types , from_date , to_date , avg ])
180+ return cursor .fetchall ()
202181
203182
204183class CityView (mixins .ListModelMixin , viewsets .GenericViewSet ):
@@ -225,7 +204,8 @@ def list(self, request):
225204 moved_to = None
226205 # Get data stats from 5mins before last_data_received_at
227206 if last_data_received_at :
228- last_5_mins = last_data_received_at - datetime .timedelta (minutes = 5 )
207+ last_5_mins = last_data_received_at - \
208+ datetime .timedelta (minutes = 5 )
229209 stats = (
230210 SensorDataValue .objects .filter (
231211 Q (sensordata__sensor__node = last_active .node .id ),
0 commit comments