From 21f2e147576d5e57d0542ed67811558dfa995812 Mon Sep 17 00:00:00 2001
From: Mattia Mancini <mancini@astron.nl>
Date: Thu, 22 Nov 2018 14:27:40 +0000
Subject: [PATCH] OSB-34: minor fixes and implementation of the plot generation
 once a plot is requested.

I made sure that if a time span contains observation with different mode but with modes regarding the same
component the results are still returned for both. Additionally, a certain mode may not be for a long period.
So givin a mode a, it can happen that a certain component in the RTSM is not tested and result as green. This
a long standing issue. To solve this issue the RTSM should provide the mode in which the observation is perfomed. However, to mitigate the issue I am now excluding all the observations that do not have errors in a specific mode but have errors in another mode. This can still include spurious errors if such observation does not presents any RTSM error! ('luckly' it is very rare).
---
 .../monitoringdb/views/controllers.py         | 302 ++++++++++++------
 1 file changed, 208 insertions(+), 94 deletions(-)

diff --git a/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py b/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py
index 47a2fa6440a..90afd381296 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py
@@ -7,18 +7,19 @@ import coreapi
 import coreschema
 from django.db.models import Count
 from lofar.maintenance.monitoringdb.models.component_error import ComponentError
-from lofar.maintenance.monitoringdb.models.rtsm import RTSMErrorSummary, MODE_TO_COMPONENT
+from lofar.maintenance.monitoringdb.models.rtsm import RTSMErrorSummary, MODE_TO_COMPONENT, COMPONENT_TO_MODE
 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 rest_framework import status
+from rest_framework.reverse import reverse
 from rest_framework.response import Response
 from rest_framework.schemas import ManualSchema
 from rest_framework.views import APIView
 import pytz
 
-logger = logging.getLogger(__name__)
 
+logger = logging.getLogger(__name__)
 
 def parse_date(date):
     expected_format = '%Y-%m-%d'
@@ -92,9 +93,12 @@ class ValidableReadOnlyView(APIView):
                     self.__setattr__(field.name, value)
                 errors = field.schema.validate(self.__getattribute__(field.name))
                 for error in errors:
-                    raise ValueError(error.text)
+                    raise ValueError(" ".join([field.name, error.text]))
 
     def get(self, request):
+        # Store the request as attribute
+        self.request = request
+
         try:
             self.validate_query_parameters(request)
         except ValueError as e:
@@ -761,69 +765,6 @@ class ControllerStationComponentErrors(ValidableReadOnlyView):
 
         return response_payload
 
-    def compose_image_storage_url(self,station_name, observation_id, start_date, rcu, component_type, error_type, both_polarization):
-        """
-        WARNING -----------------------------------------------------------
-        This is an ugly function that has to be removed as soon as possible
-        and it is meant to provide a url to the RTSM plots to have it displayed
-        in the webview. A way to proper implement this part is still missing
-        hence this function.
-        DONT BLAME THE PLAYER BLAME THE GAME!
-        -------------------------------------------------------------------
-        :param observation_id: id of the observation (SASID)
-        :param start_date: start date of the observation
-        :param rcu: rcu number
-        :param error_type: type of error
-        :return:
-        """
-        # the sample link address looks like
-        # https://proxy.lofar.eu/rtsm/obs_plots/20181108_1542_683264/683264_SE607C_rcu175_sn.png
-        baseURL = settings.URL_TO_RTSM_PLOTS
-
-        TO_SHORT_ERROR_TYPE = dict(SUMMATOR_NOISE='sn',
-                                   HIGH_NOISE='hn',
-                                   LOW_NOISE='ln',
-                                   FLAT='flat',
-                                   SHORT='short',
-                                   OSCILLATION='osc',
-                                   DOWN='down'
-                                   )
-
-        if component_type in ['LBL', 'LBH'] and both_polarization:
-            TO_SHORT_ERROR_TYPE.pop('FLAT')
-            type = 'ant'
-            component_id = rcu // 2
-            if component_type == 'LBL':
-                component_id += 48
-        else:
-            type = 'rcu'
-            component_id = rcu
-
-        # If the error is not in the dict above there is not such plot on disk.
-        # Hence,  returns 'url not present'
-        try:
-            short_error_type = TO_SHORT_ERROR_TYPE[error_type]
-        except KeyError:
-            return 'url not present'
-
-        imageURL = '%(baseURL)s/%(start_date)s_%(observation_id)s/%(observation_id)s_%(station_name)s_%(type)s%(component_id)s_%(error_type)s.png' % dict(
-            baseURL=baseURL,
-            observation_id=observation_id,
-            start_date=start_date.strftime('%Y%m%d_%H%M'),
-            station_name=station_name,
-            component_id=component_id,
-            type=type,
-            error_type=short_error_type
-        )
-        return imageURL
-
-    def find_other_polarization_given_rcu_number(self, rcu_number):
-        # if it is even
-        if rcu_number % 2 == 0:
-            return rcu_number + 1
-        else:
-            return rcu_number - 1
-
     def collect_rtsm_errors(self):
         station_entry = Station.objects.filter(name=self.station_name).first()
         response_payload = OrderedDict()
@@ -839,15 +780,13 @@ class ControllerStationComponentErrors(ValidableReadOnlyView):
 
             rtsm_errors_per_component_type = list()
 
-            response_payload[MODE_TO_COMPONENT[observing_mode]] = rtsm_errors_per_component_type
-
             for rtsm_observation in rtsm_observations.order_by('-start_datetime'):
                 rtsm_summary = OrderedDict()
-                rtsm_errors_per_component_type.append(rtsm_summary)
 
                 rtsm_summary['test_type'] = 'R'
                 rtsm_summary['start_date'] = rtsm_observation.start_datetime
                 rtsm_summary['end_date'] = rtsm_observation.end_datetime
+                rtsm_summary['observation_id'] = rtsm_observation.observation_id
                 component_errors_dict = OrderedDict()
                 rtsm_summary['component_errors'] = component_errors_dict
 
@@ -855,11 +794,17 @@ class ControllerStationComponentErrors(ValidableReadOnlyView):
                     .filter(mode=observing_mode)\
                     .values('error_type', 'start_frequency',
                             'stop_frequency', 'percentage',
-                            'error_type', 'count', 'rcu')
+                            'error_type', 'count', 'rcu', 'pk')
+                if component_errors.count() == 0 and rtsm_observation.errors_summary.count() !=0:
+                    continue
+                else:
+                    rtsm_errors_per_component_type.append(rtsm_summary)
 
                 if self.error_types:
                     component_errors = component_errors.filter(error_type__in=self.error_types)
+
                 for component_error in component_errors:
+
                     component_id = component_error['rcu']
                     details = dict(percentage = component_error['percentage'],
                                    start_frequency = component_error['start_frequency'],
@@ -867,27 +812,25 @@ class ControllerStationComponentErrors(ValidableReadOnlyView):
                                    count = component_error['count'])
                     error_type = component_error['error_type']
                     # CHECKS IF THE ERROR IS PRESENT IN BOTH RCUS (hence, both polarizations of the antenna)
-                    both_polarization = len(component_errors.filter(rcu=self.find_other_polarization_given_rcu_number(component_id),
-                                                                    error_type=error_type)) == 1
-
-                    details['url'] = self.compose_image_storage_url(self.station_name,
-                                                                    rtsm_observation.observation_id,
-                                                                    rtsm_observation.start_datetime,
-                                                                    component_id,
-                                                                    MODE_TO_COMPONENT[observing_mode],
-                                                                    error_type,
-                                                                    both_polarization)
+                    url_to_plot = reverse('rtsm-summary-plot', (component_error['pk'],), request=self.request)
+                    details['url'] = url_to_plot
                     if component_id not in component_errors:
                         component_errors_dict[str(component_id)] = list()
                     component_errors_dict[str(component_id)] += [dict(error_type=error_type, details=details)]
 
-                #self.decorate_component_errors_with_url(component_errors_dict, rtsm_observation.observation_id, rtsm_observation.start_datetime, MODE_TO_COMPONENT[observing_mode])
+            component_name = MODE_TO_COMPONENT[observing_mode]
+            if component_name not in response_payload:
+                response_payload[component_name] = rtsm_errors_per_component_type
+            else:
+                response_payload[component_name] += rtsm_errors_per_component_type
+
         return response_payload
 
     def compute_response(self):
 
         self.from_date = parse_date(self.from_date)
         self.to_date = parse_date(self.to_date)
+
         station_test_errors = {}
         rtsm_errors = {}
 
@@ -906,26 +849,197 @@ class ControllerStationComponentErrors(ValidableReadOnlyView):
 
         return Response(status=status.HTTP_200_OK, data=payload)
 
-from ..tasks import greetings, check_observation_plots
 
-class ControllerTestCeleryQueue(ValidableReadOnlyView):
-    observation_database_id = None
+class ControllerStationComponentElementErrors(ValidableReadOnlyView):
+    station_name= None  # required
+    from_date= None  # required
+    to_date= None  # required
+    component_type= None  # required
+    antenna_id= None  # required
+    test_type= "A"
+
     fields = [
         coreapi.Field(
-            'observation_database_id',
+            'station_name',
+            required=True,
+            location='query',
+            schema=coreschema.String(description='name of the station to select')
+        ),
+        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 from date (ex. YYYY-MM-DD)')
+        ),
+        coreapi.Field(
+            'component_type',
+            required=True,
+            location='query',
+            schema=coreschema.Enum(
+                ['HBA', 'LBH', 'LBL'],
+                description='select the antenna type. Possible values are (HBA, LBH, LBL)'
+            )
+        ),
+        coreapi.Field(
+            'antenna_id',
+            required=True,
+            location='query',
             type=int,
+            schema=coreschema.Integer(description='Select the antenna id')
+        ),
+        coreapi.Field(
+            'test_type',
+            required=False,
             location='query',
+            schema=coreschema.Enum(
+                ['R', 'S', 'A'],
+                description='select the type of test possible values are (R, RTSM),'
+                            ' (S, Station test), (A, all)[DEFAULT=A]',
+            )
+        )
+    ]
 
-            schema=coreschema.Integer(description='observation_id')
-        )]
-    def compute_response(self):
-        from celery.result import AsyncResult
-        task_id = 'print_last_observation_%s' % self.observation_database_id
+    def rcu_from_antenna_type_polarization(self, antenna_id, type, polarization):
+        """
+        Compute the rcu number for a given antenna number, type and polarization
+        :param antenna_id: id of the antenna
+        :param type: type of the antenna
+        :param polarization: polarization either [X, Y]
+        :return: the rcu id
+        :rtype: int
+        """
+        if polarization not in ['X', 'Y']:
+            raise ValueError('Polarization has to be either X or Y: %s not recognized' % polarization)
+
+        if type == 'LBH':
+            rcu_id = antenna_id * 2
+            rcu_id += 1 if polarization == 'X' else 0
+        elif type == 'LBL':
+            rcu_id = (antenna_id - 48) * 2
+            rcu_id += 0 if polarization == 'X' else 1
+        elif type == 'HBA':
+            rcu_id = antenna_id
+            rcu_id += 1 if polarization == 'X' else 0
+        else:
+            rcu_id = -1
+        return rcu_id
+
+    def rcus_from_antenna_and_type(self, antenna_id, type):
+        rcu_x = self.rcu_from_antenna_type_polarization(antenna_id, type, 'X')
+        rcu_y = self.rcu_from_antenna_type_polarization(antenna_id, type, 'Y')
+
+        rcus={rcu_x:'X', rcu_y:'Y'}
+        return rcus
+
+    def compute_rtsm_errors_list(self):
+        errors = dict()
+        rcus_per_polarization = self.rcus_from_antenna_and_type(self.antenna_id,
+                                                                self.component_type)
+
+        rtsm_errors = RTSMErrorSummary.objects.values('observation__pk',
+                                        'observation__start_datetime',
+                                        'observation__end_datetime',
+                                        'observation__observation_id',
+                                        'observation__station__name',
+                                        'pk',
+                                        'rcu',
+                                        'mode',
+                                        'error_type',
+                                        'percentage',
+                                        'count',
+                                        'observation__samples').filter(
+            observation__start_datetime__gt=self.from_date,
+            observation__end_datetime__lt=self.to_date,
+            observation__station__name=self.station_name,
+            mode__in=COMPONENT_TO_MODE[self.component_type],
+            rcu__in=list(rcus_per_polarization.keys())).order_by('-observation__start_datetime')
+        for item in rtsm_errors:
+            observation_pk = item['observation__pk']
+            if observation_pk not in errors.keys():
+                errors[observation_pk] = dict(test_id=item['observation__observation_id'],
+                                                       db_id=item['observation__pk'],
+                                                       start_date=item[
+                                                           'observation__start_datetime'],
+                                                       end_date=item[
+                                                           'observation__end_datetime'],
+                                                       test_type='R',
+                                                       component_errors=dict())
+            rcu = item['rcu']
+            polarization = rcus_per_polarization[rcu]
+            if polarization not in errors[observation_pk]:
+                errors[observation_pk]['component_errors'][polarization] = dict(
+                    rcu=rcu,
+                    errors = dict(),
+                    element_errors = dict()
+                )
+            error_type = item['error_type']
+            percentage = item['percentage']
+            count = item['count']
+            mode = item['mode']
+            samples = item['observation__samples']
+
+            url_to_plot = reverse('rtsm-summary-plot', (item['pk'],), request=self.request)
+            errors[observation_pk]['component_errors'][polarization]['errors'][error_type] = dict(samples=samples,
+                                                                              percentage=percentage,
+                                                                              count=count,
+                                                                              mode=mode,
+                                                                              url=url_to_plot)
+        return list(errors.values())
+
+    def compute_station_tests_error_list(self):
+        errors = dict()
+        rcus_per_polarization = self.rcus_from_antenna_and_type(self.antenna_id,
+                                                                self.component_type)
+        component_errors = ComponentError.objects.filter(
+            station_test__start_datetime__gt=self.from_date,
+            station_test__end_datetime__lt=self.to_date,
+            station_test__station__name=self.station_name,
+            component__type=self.component_type,
+            component__component_id__in=list(rcus_per_polarization.keys())).order_by('-station_test__start_datetime')
+        for component_error in component_errors:
+            station_test_pk = component_error.station_test.pk
+            if station_test_pk not in errors.keys():
+                errors[station_test_pk] = dict(test_id=station_test_pk,
+                                               db_id=station_test_pk,
+                                               start_date=component_error.station_test.start_datetime,
+                                               end_date=component_error.station_test.end_datetime,
+                                               test_type='S',
+                                               component_errors=dict())
+                component_id = component_error.component.component_id
+                polarization = rcus_per_polarization[component_id]
+                if polarization not in errors[station_test_pk]:
+                    errors[station_test_pk]['component_errors'][polarization] = dict(
+                        rcu=component_id,
+                        errors=dict())
+                error_type = component_error.type
+                details = component_error.details
+                errors_per_error_polarization = errors[station_test_pk]['component_errors'][polarization]['errors']
+                errors_per_error_polarization[error_type] = dict(details=details, element_errors=dict())
+
+
+                for element in component_error.failing_elements.values('element__element_id', 'details'):
+                    element_id = element['element__element_id']
+                    errors_per_error_polarization[error_type]['element_errors'][element_id] = element['details']
+
+        return list(errors.values())
 
-        stuff = check_observation_plots.apply_async((self.observation_database_id,))
-        stuff = check_observation_plots.apply_async((self.observation_database_id,))
+    def compute_response(self):
+        self.from_date = parse_date(self.from_date)
+        self.to_date = parse_date(self.to_date)
+        rtsm_errors_list = []
+        station_test_list = []
 
-        #print(aresult.get(timeout=10))
+        if self.test_type == 'R' or self.test_type == 'A':
+            rtsm_errors_list = self.compute_rtsm_errors_list()
+        if self.test_type == 'S' or self.test_type == 'A':
+            station_test_list = self.compute_station_tests_error_list()
 
-        return Response(status=status.HTTP_200_OK, data='command sent: %s' % stuff)
\ No newline at end of file
+        combined = rtsm_errors_list + station_test_list
+        combined_and_sorted = sorted(combined, key=lambda test: test['start_date'], reverse=True)
+        return Response(status=status.HTTP_200_OK, data=dict(errors=combined_and_sorted))
-- 
GitLab