diff --git a/LCU/Maintenance/DBInterface/monitoringdb/urls.py b/LCU/Maintenance/DBInterface/monitoringdb/urls.py index 2a4f79f1c1dfbe5fc55da301e5f21c81abffffd7..a1c550458754e9c5f62f00783bed28d19d057b9a 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/urls.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/urls.py @@ -40,6 +40,7 @@ urlpatterns = [ url(r'^api/view/ctrl_stationoverview', ControllerStationOverview.as_view()), url(r'^api/view/ctrl_stationtestsummary', ControllerStationTestsSummary.as_view()), url(r'^api/view/ctrl_latest_observation', ControllerLatestObservations.as_view()), + url(r'^api/view/ctrl_stationtest_statistics', ControllerStationTestStatistics.as_view()), url(r'^api/docs', include_docs_urls(title='Monitoring DB API')) ] diff --git a/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py b/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py index ceca8d314b6b776cd508553998a25a3876b2d6af..92ff755234ef062a8424487af1a4e321eaa785e1 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py @@ -3,8 +3,11 @@ from collections import OrderedDict import coreapi import coreschema -from django.db.models import Count, Sum +from django.db.models import Count + +from math import ceil from django.db.models import Window, F + from rest_framework import status from rest_framework.response import Response from rest_framework.schemas import ManualSchema @@ -13,6 +16,8 @@ from rest_framework.views import APIView from lofar.maintenance.monitoringdb.models.rtsm import RTSMObservation from lofar.maintenance.monitoringdb.models.station import Station from lofar.maintenance.monitoringdb.models.station_test import StationTest +from lofar.maintenance.monitoringdb.models.component_error import ComponentError +from lofar.maintenance.monitoringdb.models.rtsm import RTSMErrorSummary class ControllerStationOverview(APIView): @@ -157,9 +162,8 @@ class ControllerStationTestsSummary(APIView): "station_group", required=False, location='query', - schema=coreschema.Enum(['C', 'R', 'I', 'A'], description= - 'Station group to select for choices are [C|R|I|ALL]', - ) + schema=coreschema.Enum(['C', 'R', 'I', 'A']), + description='Station group to select for choices are [C|R|I|ALL]' ), coreapi.Field( "errors_only", @@ -403,3 +407,236 @@ class ControllerLatestObservations(APIView): response_payload = sorted(response_payload, key=lambda item: item['total_component_errors'], reverse=True) return Response(status=status.HTTP_200_OK, data=response_payload) + + +class ControllerStationTestStatistics(APIView): + """ + +/views/ctrl_stationtest_statistics: + +parameters: +station_group [C|R|I|ALL] (optional, default ALL) +test_type [RTSM|STATIONTEST|BOTH] (optional, default BOTH) +from_date #DATE +to_date #DATE +averaging_interval: #TIMESPAN +result: +{ + start_date : #DATE, + end_date: #DATE, + averaging_interval: #INTERVAL, + error_per_station: [{ + time: #DATE, + station_name: <station_name> + n_errors: #nr_errors int + }, ...], + error_per_error_type: [{ + time: #DATE, + error_type: <error_type> + n_errors: #nr_errors int + }, ...], + }, + .... +] + """ + DEFAULT_STATION_GROUP = 'A' + DEFAULT_TEST_TYPE = 'B' + + queryset = StationTest.objects.all() + schema = ManualSchema(fields=[ + coreapi.Field( + "test_type", + required=False, + location='query', + schema=coreschema.Enum(['R', 'S', 'B'], + description='select the type of test possible values are (R, RTSM),' + ' (S, Station test), (B, both)[DEFAULT=B]', + ) + ), + coreapi.Field( + "station_group", + required=False, + location='query', + schema=coreschema.Enum(['C', 'R', 'I', 'A'], description= + 'Station group to select for choices are [C|R|I|ALL]', + ) + ), + coreapi.Field( + "from_date", + required=True, + location='query', + schema=coreschema.String( + description='select tests from date (ex. YYYY-MM-DD)') + ), + coreapi.Field( + "to_date", + required=True, + location='query', + schema=coreschema.String( + description='select tests to date (ex. YYYY-MM-DD)') + ), + coreapi.Field( + "averaging_interval", + required=True, + location='query', + schema=coreschema.Integer( + description='averaging interval in days') + ) + ] + ) + + @staticmethod + def parse_date(date): + expected_format = '%Y-%m-%d' + try: + parsed_date = datetime.datetime.strptime(date, expected_format) + return parsed_date + except Exception as e: + raise ValueError('cannot parse %s with format %s - %s' % (date, expected_format, e)) + + def validate_query_parameters(self, request): + self.station_group = request.query_params.get('station_group', self.DEFAULT_STATION_GROUP) + if self.station_group not in ['C', 'R', 'I', 'A']: + raise ValueError('station_group is not one of [C,R,I,A]') + + from_date = request.query_params.get('from_date') + self.from_date = ControllerLatestObservations.parse_date(from_date) + + to_date = request.query_params.get('to_date') + self.to_date = ControllerLatestObservations.parse_date(to_date) + + self.test_type = request.query_params.get('test_type', self.DEFAULT_TEST_TYPE) + + if self.test_type not in ['R', 'S', 'B']: + raise ValueError('test_type is not one of [R,S,B]') + + self.averaging_interval = datetime.timedelta(int(request.query_params.get('averaging_interval'))) + + def compute_errors_per_station(self, from_date, to_date, central_time, station_group, test_type): + + component_errors = ComponentError.objects.all() + rtsm_summary_errors = RTSMErrorSummary.objects.all() + + if station_group: + component_errors = component_errors.filter(station_test__station__type=station_group) + rtsm_summary_errors = rtsm_summary_errors.filter(observation__station__type=station_group) + + station_test_results = [] + rtsm_results = [] + if test_type in ['S', 'B']: + station_test_results = component_errors. \ + filter(station_test__start_datetime__gt=from_date, station_test__start_datetime__lt=to_date). \ + values('station_test__station__name'). \ + annotate(n_errors=Count('station_test__station__name')) + if test_type in ['R', 'B']: + rtsm_results = rtsm_summary_errors. \ + filter(observation__start_datetime__gt=from_date, observation__start_datetime__lt=to_date). \ + values('observation__station__name'). \ + annotate(n_errors=Count('observation__station__name')) + + errors_per_station_in_bin = dict() + + if test_type in ['S', 'B']: + for result in station_test_results: + station_name = result['station_test__station__name'] + errors_per_station_in_bin[station_name] = dict(station_name=station_name, + n_errors=result['n_errors'], + time=central_time) + + if test_type in ['R', 'B']: + for result in rtsm_results: + station_name = result['observation__station__name'] + if station_name not in errors_per_station_in_bin: + errors_per_station_in_bin[station_name] = dict(station_name=station_name, + n_errors=result['n_errors'], + time=central_time) + else: + errors_per_station_in_bin[station_name]['n_errors'] += result['n_errors'] + + return errors_per_station_in_bin.values() + + def compute_errors_per_type(self, from_date, to_date, central_time, station_group, test_type): + + component_errors = ComponentError.objects.all() + rtsm_summary_errors = RTSMErrorSummary.objects.all() + + station_test_results = [] + rtsm_results = [] + + if station_group: + component_errors = component_errors.filter(station_test__station__type=station_group) + rtsm_summary_errors = rtsm_summary_errors.filter(observation__station__type=station_group) + + if test_type in ['S', 'B']: + station_test_results = component_errors. \ + filter(station_test__start_datetime__gt=from_date, station_test__start_datetime__lt=to_date). \ + values('type'). \ + annotate(n_errors=Count('type')) + if test_type in ['R', 'B']: + rtsm_results = rtsm_summary_errors. \ + filter(observation__start_datetime__gt=from_date, observation__start_datetime__lt=to_date). \ + values('error_type'). \ + annotate(n_errors=Count('error_type')) + + errors_per_error_type_in_bin = dict() + + if test_type in ['S', 'B']: + for result in station_test_results: + error_type = result['type'] + errors_per_error_type_in_bin[error_type] = dict(error_type=error_type, + n_errors=result['n_errors'], + time=central_time) + if test_type in ['R', 'B']: + for result in rtsm_results: + error_type = result['error_type'] + if error_type not in errors_per_error_type_in_bin: + errors_per_error_type_in_bin[error_type] = dict(error_type=error_type, + n_errors=result['n_errors'], + time=central_time) + else: + errors_per_error_type_in_bin[error_type]['n_errors'] += result['n_errors'] + + return errors_per_error_type_in_bin.values() + + def get(self, request, format=None): + try: + self.validate_query_parameters(request) + except ValueError as e: + return Response(status=status.HTTP_406_NOT_ACCEPTABLE, + data='Error wrong format: %s' % (e,)) + except KeyError as e: + return Response(status=status.HTTP_406_NOT_ACCEPTABLE, + data='Please specify all the correct parameters: %s' % (e,)) + + response_payload = OrderedDict() + + response_payload['start_date'] = self.from_date + response_payload['end_date'] = self.to_date + response_payload['averaging_interval'] = self.averaging_interval + + errors_per_station = [] + errors_per_type = [] + n_bins = int(ceil((self.to_date - self.from_date) / self.averaging_interval)) + + for i in range(n_bins): + if self.station_group is 'A': + station_group = None + else: + station_group = self.station_group + errors_per_station.append( + self.compute_errors_per_station(from_date=self.from_date + i * self.averaging_interval, + to_date=self.from_date + (i + 1) * self.averaging_interval, + central_time=self.from_date + (i + .5) * self.averaging_interval, + station_group=station_group, + test_type=self.test_type)) + errors_per_type.append( + self.compute_errors_per_type(from_date=self.from_date + i * self.averaging_interval, + to_date=self.from_date + (i + 1) * self.averaging_interval, + central_time=self.from_date + (i + .5) * self.averaging_interval, + station_group=station_group, + test_type=self.test_type)) + + response_payload['errors_per_station'] = errors_per_station + response_payload['errors_per_type'] = errors_per_type + + return Response(status=status.HTTP_200_OK, data=response_payload)