diff --git a/.gitattributes b/.gitattributes
index ea9df6aed0c1507dc691b9d30174895c1bba1bab..b1d38bc670c26015e96cc56ead3e7829a47a519d 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1900,6 +1900,11 @@ LCU/Maintenance/DBInterface/test/models/t_wincc_models.sh -text
 LCU/Maintenance/DBInterface/test/models/test_rtsm_models.py -text
 LCU/Maintenance/DBInterface/test/models/test_wincc_models.py -text
 LCU/Maintenance/DBInterface/test/postgres_testrunner.py -text
+LCU/Maintenance/Docker/BaseMaintenance/Dockerfile -text
+LCU/Maintenance/Docker/DBInterface/Dockerfile -text
+LCU/Maintenance/Docker/RESTService/Dockerfile -text
+LCU/Maintenance/Docker/RESTService/entrypoint.sh -text
+LCU/Maintenance/Docker/docker-compose.yml -text
 LCU/Maintenance/MDB_WebView/CMakeLists.txt -text
 LCU/Maintenance/MDB_WebView/maintenancedb_view/.env -text
 LCU/Maintenance/MDB_WebView/maintenancedb_view/CMakeLists.txt -text
@@ -2034,6 +2039,7 @@ LCU/Maintenance/MDB_tools/fabfile.py -text
 LCU/Maintenance/MDB_tools/lib/CMakeLists.txt -text
 LCU/Maintenance/MDB_tools/lib/__init__.py -text
 LCU/Maintenance/MDB_tools/lib/client.py -text
+LCU/Maintenance/MDB_tools/lib/tests_validators.py -text
 LCU/Maintenance/MDB_tools/requirements.txt -text
 LCU/Maintenance/MDB_tools/test/output_testing.py -text
 LCU/Maintenance/MDB_tools/test/python-coverage.sh eol=lf
diff --git a/LCU/Maintenance/DBInterface/CMakeLists.txt b/LCU/Maintenance/DBInterface/CMakeLists.txt
index 64e35b8fb80d746185de6670eb5d9075f973e9d0..f942add6ed7089c4c445e191f2c2262685e715d4 100644
--- a/LCU/Maintenance/DBInterface/CMakeLists.txt
+++ b/LCU/Maintenance/DBInterface/CMakeLists.txt
@@ -3,7 +3,7 @@
 set(USE_PYTHON_COMPILATION Off)
 lofar_find_package(Python 3.0 REQUIRED)
 
-lofar_package(DBInterface 1.0 DEPENDS PyCommon)
+lofar_package(DBInterface 1.0 DEPENDS PyCommon MDB_tools)
 
 include(PythonInstall)
 
@@ -23,10 +23,9 @@ foreach(LIST_ITEM ${PY_FILES})
     MESSAGE(STATUS ${LIST_ITEM})
 endforeach(LIST_ITEM ${PY_FILES})
 
-
-
 add_subdirectory(bin)
 add_subdirectory(test)
 
 python_install(${PY_FILES} DESTINATION lofar/maintenance)
 
+include(CPack)
diff --git a/LCU/Maintenance/DBInterface/django_postgresql/settings.py b/LCU/Maintenance/DBInterface/django_postgresql/settings.py
index 76c618c60fd6ea277dd3d139a48cd37d3d9aac1b..51e3d479e96fa951be8d879ea3b85930266182b8 100644
--- a/LCU/Maintenance/DBInterface/django_postgresql/settings.py
+++ b/LCU/Maintenance/DBInterface/django_postgresql/settings.py
@@ -194,6 +194,9 @@ USE_TZ = True
 PATH = os.getcwd()
 STATIC_ROOT = os.path.join(PATH, "static")
 STATIC_URL = '/static/'
+MB_CONST = 1024 ** 3
+
+DATA_UPLOAD_MAX_MEMORY_SIZE = 500 * MB_CONST
 
 REST_FRAMEWORK = {
     'DEFAULT_PAGINATION_CLASS': 'lofar.maintenance.monitoringdb.pagination.DefaultPaginationSettings',
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/models/component.py b/LCU/Maintenance/DBInterface/monitoringdb/models/component.py
index 7995893b53995ea3b76898608b6e042bc4c9c4aa..7ad4f71c8d2bd8edc59eba7ff2d9062f1e7d05d7 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/models/component.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/models/component.py
@@ -1,7 +1,8 @@
-from django.db import models
-from .station import Station
 from typing import Tuple
 
+from django.db import models
+
+from .station import Station
 
 HBA_ELEMENT_ID_RANGE = range(0, 16, 1)
 
@@ -14,7 +15,7 @@ def antenna_type_antenna_id_to_component_type_component_id(station_name,
     if station_name.startswith('CS') or station_name.startswith('RS'):
         if antenna_id >= 48:
             component_type = 'LBL'
-            component_id = antenna_id - 48
+            component_id = antenna_id
         else:
             component_type = 'LBH'
             component_id = antenna_id
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/models/rtsm.py b/LCU/Maintenance/DBInterface/monitoringdb/models/rtsm.py
index af3101d631b893ab752620ee42390a8b1cb843a6..a9f957a3d21a6defefb5fa1fe2058995bc3f77b9 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/models/rtsm.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/models/rtsm.py
@@ -1,8 +1,8 @@
 """
 In this file all the model regarding the real time station monitor output are collected
 """
-from django.db import models
 from django.contrib.postgres.fields import ArrayField
+from django.db import models
 
 from .component import Component
 from .test import GenericTest
@@ -17,6 +17,14 @@ MODE_TO_COMPONENT = {
     7: 'HBA'
 }
 
+MODE_TO_FREQ_RANGE = {1: (10, 90),
+                      2: (30, 90),
+                      3: (10, 90),
+                      4: (30, 90),
+                      5: (110, 190),
+                      6: (170, 230),
+                      7: (210, 250)}
+
 
 def antenna_id_polarization_from_rcu_type(rcu, type):
     """
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/models/signals/generic_test_insert.py b/LCU/Maintenance/DBInterface/monitoringdb/models/signals/generic_test_insert.py
index 9a4ae82847bb4178251aab50210a6b4f0743192a..eb7e6e9e84ec957f3d6e862930934a964b84b9af 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/models/signals/generic_test_insert.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/models/signals/generic_test_insert.py
@@ -1,3 +1,4 @@
+import logging
 from typing import List
 
 from ..component import HBA_ELEMENT_ID_RANGE, Component, \
@@ -8,6 +9,8 @@ from ..wincc import ElementStatus, \
     ComponentStatus, \
     StationStatus, WinCCAntennaStatus, STATUS_CODE_TO_STATUS, latest_status_per_station_raw
 
+logger = logging.getLogger(__name__)
+
 
 def insert_hba_element_status(component_status: ComponentStatus,
                               hba_wincc_status: WinCCAntennaStatus):
@@ -65,16 +68,27 @@ def insert_station_status(test_instance: GenericTest, statuses: List[WinCCAntenn
     station_status_instance.save()
 
     test_instance.station_status = station_status_instance
+    test_instance.save()
     for status in statuses:
         insert_component_status(station_status_instance, status)
 
 
+def insert_station_status_per_test(test: GenericTest):
+    queryset = WinCCAntennaStatus.objects.all().order_by()
+    query_results = list(latest_status_per_station_raw(queryset=queryset,
+                                                       station=test.station.name,
+                                                       to_date=test.start_datetime))
+
+    insert_station_status(test, query_results)
+
+
+from django.db.models import signals
 def on_inserting_test(sender, instance: GenericTest, **kwargs):
+
     logger.info('received post_insert signal from %s for instance %s', sender, instance)
-    queryset = WinCCAntennaStatus.objects.all().order_by()
-    query_results = [wincc_status for wincc_status in
-                     latest_status_per_station_raw(queryset=queryset,
-                                                   station=instance.station.name,
-                                                   to_date=instance.start_datetime)]
+    signals.post_save.disconnect(on_inserting_test, sender=sender)
 
-    insert_station_status(instance, query_results)
+    try:
+        insert_station_status_per_test(instance)
+    finally:
+        signals.post_save.connect(on_inserting_test, sender=sender)
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/models/test.py b/LCU/Maintenance/DBInterface/monitoringdb/models/test.py
index b7565bde2e2c2d96c244861448e78d1cead3b48c..5e62a5f96dc2b3aa02e18089e1257bf157eed3b3 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/models/test.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/models/test.py
@@ -15,5 +15,5 @@ class GenericTest(models.Model):
     station = models.ForeignKey(Station, on_delete=models.DO_NOTHING, related_name='tests')
 
     station_status = models.OneToOneField(StationStatus,
-                                          on_delete=models.CASCADE,
+                                          on_delete=models.SET_NULL,
                                           null=True)
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/models/wincc.py b/LCU/Maintenance/DBInterface/monitoringdb/models/wincc.py
index 0e9d2bef37bb535f50a92d47e3a580f08affef8f..3d4f9b9a84190f88a47cefd0166b095d06cd306d 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/models/wincc.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/models/wincc.py
@@ -3,6 +3,7 @@ from collections import OrderedDict
 import django.db.models as models
 
 from .component import Component
+from .component import antenna_type_antenna_id_to_component_type_component_id
 from .element import Element
 from .station import Station
 
@@ -29,7 +30,8 @@ def _format_result(raw_query_result, antenna_type):
     results = OrderedDict()
     for row in raw_query_result:
         status_description = OrderedDict()
-        results[str(row.antenna_id)] = status_description
+        antenna_id = row.antenna_id
+        results[str(antenna_id)] = status_description
         status_description['inserted_at'] = row.last_entry_timestamp
         status_description['status'] = to_status(row.status_code)
         status_description['status_code'] = row.status_code
@@ -59,8 +61,6 @@ def latest_status_per_station_raw(queryset, station, to_date):
     :param queryset: source queryset
     :param station: station name
     :type station: str
-    :param component_type: component type name
-    :type component_type: str
     :param to_date: select the latest test before the to date
     :type to_date: datetime.dattime
     :return: the status of an antenna per antenna id
@@ -71,7 +71,7 @@ def latest_status_per_station_raw(queryset, station, to_date):
     INNER JOIN (
     SELECT antenna_type, antenna_id, MAX("timestamp") last_entry_timestamp 
     FROM antenna_statuses
-    WHERE timestamp <= %(to_date)s AND station=%(station_name)s
+    WHERE status_code_modified_at <= %(to_date)s AND station=%(station_name)s
     GROUP BY antenna_type, antenna_id 
     ) slave ON master.antenna_type=slave.antenna_type AND master.antenna_id = slave.antenna_id AND last_entry_timestamp=master.timestamp
     WHERE master.station=%(station_name)s
@@ -98,11 +98,12 @@ def latest_status_per_station_and_antenna_type(queryset, station, antenna_type,
     INNER JOIN (
     SELECT antenna_type, antenna_id, MAX("timestamp") last_entry_timestamp 
     FROM antenna_statuses
-    WHERE timestamp <= %s AND station=%s
+    WHERE status_code_modified_at <= %s AND station=%s
     GROUP BY antenna_type, antenna_id 
-    ) slave ON master.station=slave.station AND master.antenna_type=slave.antenna_type AND master.antenna_id = slave.antenna_id AND last_entry_timestamp=master.timestamp
+    ) slave ON master.antenna_type=slave.antenna_type AND master.antenna_id = slave.antenna_id AND last_entry_timestamp=master.timestamp
     WHERE master.station=%s AND master.antenna_type=%s
-    ''', [to_date, station.rstrip('C'), antenna_type])
+    ''', [to_date, station.rstrip('C'), station.rstrip('C'), antenna_type])
+
 
     return _format_result(raw_results, antenna_type)
 
@@ -121,8 +122,13 @@ def latest_status_per_station_and_component_type(queryset, station, component_ty
     """
     antenna_type = to_antenna_type(component_type)
     if antenna_type:
-        return latest_status_per_station_and_antenna_type(queryset, station, antenna_type,
-                                                          to_date)
+
+        results = latest_status_per_station_and_antenna_type(queryset, station, antenna_type,
+                                                             to_date)
+        return {antenna_id: results[antenna_id] for antenna_id in results
+                if antenna_type_antenna_id_to_component_type_component_id(station, component_type,
+                                                                          int(antenna_id))[
+                    0] == component_type}
     else:
         return OrderedDict()
 
@@ -278,10 +284,12 @@ class StationStatus(models.Model):
 
 
 class ComponentStatus(EntityStatus):
-    station_status = models.ForeignKey(StationStatus, on_delete=models.CASCADE)
+    station_status = models.ForeignKey(StationStatus, on_delete=models.CASCADE,
+                                       related_name='component_status')
     component = models.ForeignKey(Component, on_delete=models.DO_NOTHING)
 
 
 class ElementStatus(EntityStatus):
-    component_status = models.ForeignKey(ComponentStatus, on_delete=models.CASCADE)
+    component_status = models.ForeignKey(ComponentStatus, on_delete=models.CASCADE,
+                                         related_name='element_status')
     element = models.ForeignKey(Element, on_delete=models.DO_NOTHING)
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/rtsm_test_raw_parser.py b/LCU/Maintenance/DBInterface/monitoringdb/rtsm_test_raw_parser.py
index f1ff3df1e2950c13a42ca847009b0416260e4113..bf9ac68f0b22055c50d82b812ba77713b84ea3fa 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/rtsm_test_raw_parser.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/rtsm_test_raw_parser.py
@@ -1,6 +1,10 @@
+import logging
 from datetime import datetime
+
 import pytz
 
+logger = logging.getLogger('serializers')
+
 
 rtsm_error_types_to_error_types = dict(HN='HIGH_NOISE',
                                        SN='SUMMATOR_NOISE',
@@ -12,6 +16,7 @@ rtsm_error_types_to_error_types = dict(HN='HIGH_NOISE',
                                        DOWN='DOWN',
                                        SHORT='SHORT')
 
+
 def split_raw_rtsm_test(content):
     """
     Split the raw rtsm test in row and filters out comments and empty lines
@@ -20,7 +25,7 @@ def split_raw_rtsm_test(content):
     results = filter(lambda x: not x.startswith('#'),
                      filter(lambda x: len(x) > 0,
                             map(lambda x: x.strip(),
-                                content.split('\n')
+                                content.splitlines()
                                 )
                             ))
     return results
@@ -51,7 +56,8 @@ def preparse_rtsm_test_file(content):
                 results.append(result)
                 result = dict()
 
-            rcu, mode, observation_id, error_type, start_frequency, stop_frequency, time = value.split(',')
+            rcu, mode, observation_id, error_type, start_frequency, stop_frequency, time = value.split(
+                ',')
             result.update(rcu=int(rcu),
                           mode=int(mode),
                           error_type=rtsm_error_types_to_error_types[error_type],
@@ -79,6 +85,7 @@ def group_samples_by_error_type(content):
     errors = content.pop('errors')
     grouped_errors = dict()
     for error in errors:
+        print(error)
         key = (error['mode'], error['rcu'], error['error_type'])
         time = error.pop('time')
         average_spectrum = error.pop('average_spectrum')
@@ -91,8 +98,8 @@ def group_samples_by_error_type(content):
             grouped_errors[key]['samples'] += [sample]
         else:
             grouped_errors[key] = dict(
-                count = 1,
-                samples = [sample],
+                count=1,
+                samples=[sample],
                 **error
             )
     content['errors'] = list(grouped_errors.values())
@@ -106,4 +113,4 @@ def parse_rtsm_test(content):
     :param content: string content with the station test output
     :return: a list of Django models
     """
-    return group_samples_by_error_type(preparse_rtsm_test_file(split_raw_rtsm_test(content)))
\ No newline at end of file
+    return group_samples_by_error_type(preparse_rtsm_test_file(split_raw_rtsm_test(content)))
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/serializers/wincc.py b/LCU/Maintenance/DBInterface/monitoringdb/serializers/wincc.py
index cef8b708b4b1e95830c5df82ff06f46b0801921b..69b8d7a107ed11a2d134713e88af15808f98d9fa 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/serializers/wincc.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/serializers/wincc.py
@@ -1,8 +1,61 @@
-from ..models.wincc import WinCCAntennaStatus
+from collections import OrderedDict
+
 from .utils import NotNullModelSerializer
+from ..models.wincc import WinCCAntennaStatus, StationStatus, ComponentStatus, ElementStatus
+from ..serializers.component import ComponentSerializer
 
 
 class WinCCAntennaStatusSerializer(NotNullModelSerializer):
     class Meta:
         model = WinCCAntennaStatus
         fields = '__all__'
+
+
+class ElementStatusSerializer(NotNullModelSerializer):
+    class Meta:
+        model = ElementStatus
+        fields = '__all__'
+
+
+class ComponentStatusSerializer(NotNullModelSerializer):
+    component = ComponentSerializer()
+
+    class Meta:
+        model = ComponentStatus
+        fields = '__all__'
+
+
+class SummaryComponentStatusSerializer(NotNullModelSerializer):
+    component = ComponentSerializer()
+
+    class Meta:
+        model = ComponentStatus
+        fields = ['status_code', 'status_description', 'status_message', 'modified_at']
+
+
+class StationStatusSerializer(NotNullModelSerializer):
+    component_status = ComponentStatusSerializer()
+
+    class Meta:
+        model = StationStatus
+        fields = '__all__'
+
+
+from typing import List
+
+
+def serialize_station_status(component_statuses: List[ComponentStatus], type: str):
+    station_status = OrderedDict()
+    for component_status in component_statuses:
+        component_id = component_status.component.component_id
+        component_type = component_status.component.type
+        if component_type != type:
+            continue
+        station_status[str(component_id)] = dict(
+            status_code=component_status.status_code,
+            status=component_status.status_description,
+            reason=component_status.status_message,
+            last_modified=component_status.modified_at
+        )
+
+    return station_status
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/station_test_raw_parser.py b/LCU/Maintenance/DBInterface/monitoringdb/station_test_raw_parser.py
index 9d69bbad43de598d55d20086cac1afed45c490ee..f6fd4b5b0209b7bd5d2a483ce2c9f38e02be418c 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/station_test_raw_parser.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/station_test_raw_parser.py
@@ -2,13 +2,15 @@
 This modules contains all the function needed to parse a raw station test output into the models used to describe it
 """
 import logging
+import re
+from collections import defaultdict
 from datetime import datetime
 
 import pytz
-import re
-from collections import defaultdict
 
-logger = logging.getLogger('station_test.parser')
+logger = logging.getLogger(__name__)
+
+
 
 
 def station_type_from_station_name(station_name):
@@ -59,6 +61,15 @@ def parse_datetime(date, date_time):
                 datetime.strptime("T".join([date, date_time]), '%d-%m-%YT%H:%M:%S'))
 
 
+def split_lines_and_filter_comments(content):
+    split_lines = content.splitlines()
+
+    while split_lines.count(''):
+        split_lines.pop('')
+    split_lines = list(filter(lambda line: not line.startswith('#'), split_lines))
+    return split_lines
+
+
 def parse_raw_station_test(content):
     """
     Expects a string content with the station test output
@@ -66,14 +77,14 @@ def parse_raw_station_test(content):
     :param content: string content with the station test output
     :return: a list of Django models
     """
-    logger.debug('content splitted %s', content.split('\n'))
-    station_tests = split_history_into_tests(content.split('\n'))
+
+    station_tests = split_history_into_tests(split_lines_and_filter_comments(content))
     logger.debug('station_tests_are %s', station_tests)
     results = []
     for stest in station_tests:
         dict_stest = dict_from_raw_station_test(stest)
-        logger.debug('parsed test %s', dict_stest)
-        if dict_stest['station']['name'] == 'Unknown':
+        logger.debug('parsed test %s' % dict_stest)
+        if 'station' not in dict_stest or dict_stest['station']['name'] == 'Unknown':
             logger.error('error in the station name for test %s', dict_stest)
             continue
         if 'start_datetime' in dict_stest:
@@ -87,8 +98,7 @@ def split_history_into_tests(content):
     all_tests = []
     current_test = []
     for i, line in enumerate(content[:-1]):
-        if line.startswith('#'):
-            continue
+
         next_line_columns = content[i + 1].split(',')
         line_type = next_line_columns[3]
         current_test.append(line)
@@ -186,7 +196,6 @@ def dict_from_component_error(content):
 
     component = dict(component_id=parse_component_id(component_id), type=component_type.strip())
 
-
     component_error = dict(component=component,
                            details=error_details,
                            type=error_type.strip())
@@ -270,7 +279,6 @@ element_error_name_mapping = {
     '': 'RF_FAIL'
 }
 
-
 def parse_rffail_string(polarization, rf_string):
     """
     Parse the string for the rffail test into a dict
@@ -286,8 +294,8 @@ def parse_rffail_string(polarization, rf_string):
     LBA_RF_FAIL_NPARAMETERS = 1
 
     if HBA_RF_FAIL_NPARAMETERS == len(parameters):
-        measured_signal_nodelay, measured_signal_fulldelay, subband_used_nodelay,\
-        subband_used_fulldelay,\
+        measured_signal_nodelay, measured_signal_fulldelay, subband_used_nodelay, \
+        subband_used_fulldelay, \
         reference_signal_nodelay, reference_signal_fulldelay = map(float,
                                                                    rf_string.replace('nan', '999').
                                                                    replace('-1', '999').
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/tasks/generate_plots.py b/LCU/Maintenance/DBInterface/monitoringdb/tasks/generate_plots.py
index f3b77f45522a247176da100bab8b5aaff7674198..557b12ab0b779c8c6e01590f2e8f9660c678f902 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/tasks/generate_plots.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/tasks/generate_plots.py
@@ -1,13 +1,10 @@
 import logging
 import os
-try:
+import matplotlib
 
-    import matplotlib
+matplotlib.use('agg')
+import matplotlib.pyplot as plt
 
-    matplotlib.use('agg')
-    import matplotlib.pyplot as plt
-except ImportError:
-    pass
 import numpy
 from celery import shared_task
 from django.conf import settings
@@ -167,6 +164,7 @@ def produce_plot(observation_metadata,
              transform=plt.axes().transAxes)
     plt.legend()
     plt.savefig(path)
+    logger.info('saving plot in %s', path)
     plt.close()
 
 
@@ -263,6 +261,7 @@ def check_error_summary_plot(error_summary_id):
         elif os.path.isdir(full_path):
             raise Exception('%s is a directory' % full_path)
         else:
-            logger.debug('summary error %s is complete no need to generate additional plot',
-                         summary_plot.pk)
+            logger.info(
+                'summary error %s is complete no need to generate additional plot. File in %s',
+                summary_plot.pk, full_path)
             return summary_plot.pk
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/tasks/insert_raw_tests.py b/LCU/Maintenance/DBInterface/monitoringdb/tasks/insert_raw_tests.py
index 7cdf93672d6c0c8984623d755bbb4490b162a8b3..9da82a858d478af5b39084a72593f2a88cb5c93c 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/tasks/insert_raw_tests.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/tasks/insert_raw_tests.py
@@ -6,31 +6,25 @@ from django.db import transaction
 
 # Station Models
 from lofar.maintenance.monitoringdb.models.component import Component
-from lofar.maintenance.monitoringdb.models.station import Station
+from lofar.maintenance.monitoringdb.models.component_error import ComponentError
 from lofar.maintenance.monitoringdb.models.element import Element
+from lofar.maintenance.monitoringdb.models.element_error import ElementError
 from lofar.maintenance.monitoringdb.models.log import ActionLog
-from lofar.maintenance.monitoringdb.models.signals.generic_test_insert import on_inserting_test
-
 # RTSM Models
 from lofar.maintenance.monitoringdb.models.rtsm import RTSMObservation, RTSMError, RTSMErrorSample
-
+from lofar.maintenance.monitoringdb.models.signals.generic_test_insert import on_inserting_test
+from lofar.maintenance.monitoringdb.models.station import Station
 # Station Test Models
 from lofar.maintenance.monitoringdb.models.station_test import StationTest
-from lofar.maintenance.monitoringdb.models.component_error import ComponentError
-from lofar.maintenance.monitoringdb.models.element_error import ElementError
-
 from lofar.maintenance.monitoringdb.rtsm_test_raw_parser import parse_rtsm_test
-from lofar.maintenance.monitoringdb.station_test_raw_parser import parse_raw_station_test, station_type_from_station_name
+from lofar.maintenance.monitoringdb.station_test_raw_parser import parse_raw_station_test, \
+    station_type_from_station_name
 
 logger = logging.getLogger(__name__)
-
-
-
-
 from django.db.models.signals import post_save
 
-post_save.connect(on_inserting_test, sender=RTSMObservation)
-post_save.connect(on_inserting_test, sender=StationTest)
+post_save.connect(on_inserting_test, sender=RTSMObservation, dispatch_uid='test_inserted')
+post_save.connect(on_inserting_test, sender=StationTest, dispatch_uid='test_inserted')
 
 
 def create_component_errors(station: Station, station_test: StationTest, component_errors_data):
@@ -43,7 +37,10 @@ def create_component_errors(station: Station, station_test: StationTest, compone
         component_list.append(component_entity)
         component_error_entity, _ = ComponentError.objects.get_or_create(component=component_entity,
                                                                          station_test=station_test,
-                                                                         **component_error)
+                                                                         type=component_error[
+                                                                             'type'])
+        component_error_entity.details.update(component_error['details'])
+        component_error_entity.save()
 
         component_error_list.append(component_error_entity)
 
@@ -138,7 +135,8 @@ def create_rtsm_test(rtsm_observation) -> RTSMObservation:
 
     station_name = rtsm_observation.pop('station_name')
     sas_id = rtsm_observation.pop('observation_id')
-    station, _ = create_station(dict(name=station_name,
+
+    station = create_station(dict(name=station_name,
                                      type=station_type_from_station_name(station_name)))
 
     rtsm_observation, _ = RTSMObservation.objects.get_or_create(station=station,
@@ -153,6 +151,7 @@ def create_rtsm_test(rtsm_observation) -> RTSMObservation:
 def insert_station_test(action_log_id: ActionLog, raw_tests):
     action_log = ActionLog.objects.get(pk=action_log_id)
     try:
+        logger.debug('handling raw request_data for data %s', raw_tests)
         parsed_content = parse_raw_station_test(raw_tests)
         if parsed_content is None:
             raise Exception('cannot parse test {}'.format(raw_tests))
@@ -171,11 +170,11 @@ def insert_station_test(action_log_id: ActionLog, raw_tests):
 def insert_rtsm_test(action_log_id: ActionLog, request_data: Dict):
     action_log = ActionLog.objects.get(pk=action_log_id)
     try:
-        #logger.debug('handling raw request_data for %s', request_data)
+        logger.debug('handling raw request_data for %s', request_data)
         content = request_data['content']
 
         station_name = request_data['station_name']
-        #logger.debug('handling raw request_data for data %s', content)
+        logger.debug('handling raw request_data for data %s', content)
 
         entry = parse_rtsm_test(content)
 
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/views/common.py b/LCU/Maintenance/DBInterface/monitoringdb/views/common.py
index db345cc545f47e8eb44947f03a288b88861b6862..731dbcf79f606d578ffc2a9e5f46c26540e88d92 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/views/common.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/views/common.py
@@ -1,6 +1,6 @@
 from rest_framework import viewsets, status
 from rest_framework.response import Response
-from django.http import HttpResponse
+from django.http import HttpResponse, HttpRequest
 from rest_framework.decorators import api_view
 import logging
 from ..exceptions import ItemAlreadyExists
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py b/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py
index e182e42303852678dec3394d2ede8470e877c892..a78c02da95f4c54c76327014e999c8e581270d15 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py
@@ -3,12 +3,13 @@ import logging
 from collections import OrderedDict
 from collections import defaultdict
 from math import ceil
+from typing import List
 
-from django.utils import timezone
 import coreapi
 import coreschema
 import pytz
-from django.db.models import Count
+from django.db.models import Count, Prefetch
+from django.utils import timezone
 from rest_framework import status
 from rest_framework.response import Response
 from rest_framework.reverse import reverse
@@ -20,12 +21,15 @@ from lofar.maintenance.monitoringdb.models.rtsm import RTSMError
 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.test import GenericTest
 from lofar.maintenance.monitoringdb.models.wincc import WinCCAntennaStatus, \
-    latest_status_per_station_and_component_type, \
-    latest_status_per_station_and_component_type_antenna_id
+    ComponentStatus, StationStatus, \
+    latest_status_per_station_and_component_type_antenna_id, \
+    latest_status_per_station_and_component_type
+from lofar.maintenance.monitoringdb.serializers.wincc import serialize_station_status
 
 logger = logging.getLogger(__name__)
-from lofar.maintenance.monitoringdb.models.rtsm import MODE_TO_COMPONENT
+from lofar.maintenance.monitoringdb.models.rtsm import MODE_TO_FREQ_RANGE
 
 
 def parse_date(date):
@@ -167,7 +171,6 @@ class ValidableReadOnlyView(APIView):
     def get(self, request):
         # Store the request as attribute
         self.request = request
-
         try:
             self.validate_query_parameters(request)
         except ValueError as e:
@@ -190,8 +193,9 @@ class ValidableReadOnlyView(APIView):
         return response
 
 
-def compute_error_summary(station_test:StationTest, selected_error_types=None):
+def compute_error_summary(station_test: StationTest, selected_error_types=None):
     component_error_summary = dict()
+    total = 0
 
     for component_error in station_test.component_errors.all():
         component_type = component_error.component.type
@@ -199,7 +203,7 @@ def compute_error_summary(station_test:StationTest, selected_error_types=None):
 
         if selected_error_types and component_error_type not in selected_error_types:
             continue
-
+        total += 1
         if component_type not in component_error_summary:
             component_error_summary[component_type] = {component_error_type: 1}
         elif component_error_type not in component_error_summary[component_type]:
@@ -207,7 +211,8 @@ def compute_error_summary(station_test:StationTest, selected_error_types=None):
         else:
             component_error_summary[component_type][component_error_type] += 1
 
-    return component_error_summary
+    return component_error_summary, total
+
 
 from django.db.models import Window, F
 from django.db.models.functions import Rank
@@ -270,18 +275,19 @@ class ControllerStationOverview(ValidableReadOnlyView):
 
     def get_last_station_test_per_station(self, selected_stations):
         expected_tests = len(selected_stations) * self.n_station_tests
-
-        station_test_instances = StationTest.objects.filter(station__name__in=selected_stations). \
+        station_test_instances = StationTest.objects.order_by().filter(
+            station__name__in=selected_stations). \
                                      annotate(order=Window(
             expression=Rank(),
             partition_by=[F('station')],
             order_by=F('start_datetime').desc())
-        ).order_by('order')[:expected_tests].\
+        ).order_by('order', 'station__name')[:expected_tests]. \
             select_related('station').prefetch_related('component_errors',
                                                        'component_errors__component')
 
         st_per_station = defaultdict(list)
-        for station_test in station_test_instances:
+
+        for ind, station_test in enumerate(station_test_instances):
             station_name = station_test.station.name
 
             test_summary = dict()
@@ -289,11 +295,13 @@ class ControllerStationOverview(ValidableReadOnlyView):
             test_summary.update(end_datetime=station_test.end_datetime)
             test_summary.update(checks=station_test.checks)
 
-            component_error_summary = compute_error_summary(station_test, self.error_types)
-
+            component_error_summary, total = compute_error_summary(station_test, self.error_types)
+            test_summary.update(total_component_errors=total)
             test_summary.update(component_error_summary=component_error_summary)
-
-            st_per_station[station_name] = test_summary
+            if station_name in st_per_station and len(
+                    st_per_station[station_name]) >= self.n_station_tests:
+                continue
+            st_per_station[station_name] += [test_summary]
         return st_per_station
 
     def get_last_rtsm(self, selected_stations):
@@ -345,10 +353,9 @@ class ControllerStationOverview(ValidableReadOnlyView):
         station_entities = Station.objects.all()
 
         if 'A' not in self.station_group:
-            station_entities = station_entities.filter(type__in=self.station_group)\
-
-        station_entities = list(zip(*station_entities.values_list('name')))[0]
+            station_entities = station_entities.filter(type__in=self.station_group)
 
+        station_entities = list(zip(*station_entities.values_list('name').order_by('name')))[0]
 
         # Since django preferes a ordered dict over a dict we make it happy... for now
         rtsm_per_station = self.get_last_rtsm(station_entities)
@@ -414,16 +421,17 @@ class ControllerStationTestsSummary(ValidableReadOnlyView):
 
         from_date = timezone.now() - timezone.timedelta(days=self.lookback_time)
 
-        station_test_entities = StationTest.objects.filter(start_datetime__gt=from_date).\
+        station_test_entities = StationTest.objects.filter(start_datetime__gt=from_date). \
             select_related('station')
 
         if 'A' not in self.station_group:
-            station_test_entities = station_test_entities.filter(station__type__in=self.station_group)
-
+            station_test_entities = station_test_entities.filter(station__type__in=
+                                                                 self.station_group)
 
         station_test_entities = \
             station_test_entities.prefetch_related('component_errors',
-                                                   'component_errors__component').order_by('-start_datetime')
+                                                   'component_errors__component').order_by(
+                '-start_datetime')
 
         # Since django preferes a ordered dict over a dict we make it happy... for now
         response_payload = list()
@@ -431,12 +439,14 @@ class ControllerStationTestsSummary(ValidableReadOnlyView):
         for station_test in station_test_entities:
             station_test_summary = dict()
             station_test_summary.update(station_name=station_test.station.name)
-            station_test_summary.update(total_component_errors=station_test.component_errors.count())
+            station_test_summary.update(
+                total_component_errors=station_test.component_errors.count())
             station_test_summary.update(start_datetime=station_test.start_datetime)
             station_test_summary.update(end_datetime=station_test.end_datetime)
             station_test_summary.update(date=station_test.start_datetime.strftime("%Y-%m-%d"))
 
-            component_error_summary = compute_error_summary(station_test, self.error_types)
+            component_error_summary, total = compute_error_summary(station_test, self.error_types)
+            station_test_summary.update(total_component_errors=total)
             station_test_summary.update(component_error_summary=component_error_summary)
             response_payload.append(station_test_summary)
 
@@ -503,26 +513,26 @@ class ControllerLatestObservations(ValidableReadOnlyView):
             filtered_entities = filtered_entities.exclude(errors_summary__isnull=True)
 
         errors_summary = filtered_entities \
-            .values('observation_id',
+            .values('sas_id',
                     'station__name',
                     'start_datetime',
                     'end_datetime',
-                    'errors_summary__error_type',
-                    'errors_summary__mode') \
-            .annotate(total=Count('errors_summary__error_type')) \
-            .order_by('observation_id', 'station__name')
+                    'errors__error_type',
+                    'errors__mode') \
+            .annotate(total=Count('errors__error_type')) \
+            .order_by('sas_id', 'station__name')
 
         if self.error_types:
             errors_summary = errors_summary.filter(errors_summary__error_type__in=self.error_types)
         response = dict()
 
         for error_summary in errors_summary:
-            observation_id = error_summary['observation_id']
+            observation_id = error_summary['sas_id']
             station_name = error_summary['station__name']
             start_datetime = error_summary['start_datetime']
             end_datetime = error_summary['end_datetime']
-            mode = error_summary['errors_summary__mode']
-            error_type = error_summary['errors_summary__error_type']
+            mode = error_summary['errors__mode']
+            error_type = error_summary['errors__error_type']
             total = error_summary['total']
 
             if observation_id not in response:
@@ -766,6 +776,17 @@ class ControllerAllComponentErrorTypes(ValidableReadOnlyView):
         return Response(status=status.HTTP_200_OK, data=data)
 
 
+def gather_statuses_for_test(generic_tests: List[GenericTest]):
+    statuses = {station_status.id: station_status
+                for station_status in StationStatus.objects.filter(generictest__in=generic_tests). \
+                    prefetch_related(
+        Prefetch('component_status', queryset=ComponentStatus.objects.
+                 filter(status_code__gt=10).
+                 select_related('component'),
+                 to_attr='component_statuses'))}
+    return statuses
+
+
 class ControllerStationComponentErrors(ValidableReadOnlyView):
     description = "Provides a summary per station of the component errors"
     # required parameters
@@ -818,16 +839,25 @@ class ControllerStationComponentErrors(ValidableReadOnlyView):
 
     def collect_station_test_errors(self):
 
-        component_errors = ComponentError.objects.filter(station_test__station__name=self.station_name).\
-                filter(station_test__start_datetime__range=(self.from_date, self.to_date)).\
-                select_related('station_test', 'component').\
-                prefetch_related('station_test__station','station_test__station_status').order_by('-station_test__start_datetime', 'component__station__name')
+        tests = StationTest.objects.filter(station__name=self.station_name). \
+            filter(start_datetime__range=(self.from_date, self.to_date))
+
+        component_errors = ComponentError.objects.filter(station_test__in=tests). \
+            select_related('station_test', 'component'). \
+            select_related('station_test__station_status'). \
+            order_by('-station_test__start_datetime', 'component__station__name')
 
+        if self.error_types:
+            component_errors = component_errors.filter(type__in=self.error_types)
         response_payload = OrderedDict()
 
+        statuses = gather_statuses_for_test(tests)
+
         for component_error in component_errors:
+
             component_id = component_error.component.component_id
             component_type = component_error.component.type
+
             test_id = component_error.station_test.pk
 
             component_error_summary = dict(error_type=component_error.type,
@@ -839,12 +869,13 @@ class ControllerStationComponentErrors(ValidableReadOnlyView):
             per_component_type = response_payload[component_type]
 
             if test_id not in per_component_type:
-
+                status_id = component_error.station_test.station_status.pk
+                status = statuses[status_id]
                 per_component_type[test_id] = dict(
-                    start_datetime = component_error.station_test.start_datetime,
-                    end_datetime = component_error.station_test.end_datetime,
-                    test_type = 'S',
-                    status=component_error.station_test.station_status,
+                    start_date=component_error.station_test.start_datetime,
+                    end_date=component_error.station_test.end_datetime,
+                    test_type='S',
+                    status=serialize_station_status(status.component_statuses, component_type),
                     component_errors=dict()
                 )
 
@@ -855,91 +886,75 @@ class ControllerStationComponentErrors(ValidableReadOnlyView):
             else:
                 per_test[component_id] += [component_error_summary]
 
-
         for type in response_payload:
             response_payload[type] = list(response_payload[type].values())
 
         return response_payload
 
     def collect_rtsm_errors(self):
-        station_entry = Station.objects.filter(name=self.station_name).first()
+        tests = RTSMObservation.objects.filter(station__name=self.station_name). \
+            filter(start_datetime__range=(self.from_date, self.to_date))
+
+        statuses = gather_statuses_for_test(tests)
+
+        rtsm_errors = RTSMError.objects.filter(observation__station__name=self.station_name). \
+            filter(observation__start_datetime__range=(self.from_date, self.to_date)). \
+            select_related('observation', 'component'). \
+            prefetch_related('observation__station_status',
+                             'observation__station_status__component_status')
+        if self.error_types:
+            rtsm_errors = rtsm_errors.filter(error_type__in=self.error_types)
         response_payload = OrderedDict()
-        rtsm_observations = RTSMObservation.objects.filter(station=station_entry) \
-            .filter(start_datetime__gte=self.from_date,
-                    end_datetime__lte=self.to_date)
 
-        failing_component_modes = rtsm_observations.exclude(errors__isnull=True).distinct(
-            'errors__mode').values_list('errors__mode')
-        for observing_mode in failing_component_modes:
+        for component_error in rtsm_errors:
+            component_id = component_error.component.component_id
+            component_type = component_error.component.type
+            test_id = component_error.observation.pk
+
+            polarization = component_error.polarization
+            error_type = component_error.error_type
+            url_to_plot = reverse('rtsm-summary-plot-detail', (component_error.pk,),
+                                  request=self.request)
+            start_frequency, end_frequency = MODE_TO_FREQ_RANGE[component_error.mode]
+            component_error_summary = dict(error_type=error_type,
+                                           rcu_id=component_error.rcu,
+                                           polarization=polarization,
+                                           details=dict(
+                                               url=url_to_plot,
+                                               component_id=component_id,
+                                               percentage=component_error.percentage,
+                                               n_samples=component_error.observation.samples,
+                                               start_frequency=start_frequency,
+                                               stop_frequency=end_frequency,
+                                               mode=component_error.mode
+                                           ))
 
-            observing_mode = observing_mode[0]
-            component_name = MODE_TO_COMPONENT[observing_mode]
+            if component_type not in response_payload:
+                response_payload[component_type] = dict()
 
-            rtsm_errors_per_component_type = list()
+            per_component_type = response_payload[component_type]
 
-            for rtsm_observation in rtsm_observations.order_by('-start_datetime'):
-                rtsm_summary = OrderedDict()
+            if test_id not in per_component_type:
+                status_id = component_error.observation.station_status.pk
+                status = statuses[status_id]
 
-                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.sas_id
-                antenna_statuses = latest_status_per_station_and_component_type(
-                    WinCCAntennaStatus.objects,
-                    self.station_name,
-                    component_name,
-                    to_date=rtsm_observation.end_datetime)
-                rtsm_summary['status'] = antenna_statuses
-                component_errors_dict = OrderedDict()
-                rtsm_summary['component_errors'] = component_errors_dict
-
-                component_errors = rtsm_observation.errors \
-                    .filter(mode=observing_mode) \
-                    .values('error_type', 'start_frequency',
-                            'stop_frequency', 'percentage',
-                            '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:
-
-                    rcu_id = component_error['rcu']
-                    component_id, polarization = antenna_id_polarization_from_rcu_type_polarization(
-                        rcu_id, component_name)
-                    details = dict(percentage=component_error['percentage'],
-                                   start_frequency=component_error['start_frequency'],
-                                   stop_frequency=component_error['stop_frequency'],
-                                   count=component_error['count'],
-                                   component_id=component_id)
-                    error_type = component_error['error_type']
-                    # CHECKS IF THE ERROR IS PRESENT IN BOTH RCUS (hence, both polarizations of the antenna)
-                    url_to_plot = reverse('rtsm-summary-plot-detail', (component_error['pk'],),
-                                          request=self.request)
-                    details['url'] = url_to_plot
-
-                    if not str(component_id) in component_errors_dict:
-                        component_errors_dict[str(component_id)] = OrderedDict()
-
-                    if error_type not in component_errors_dict[str(component_id)]:
-                        rtsm_error_summary = dict(error_type=error_type)
-                        component_errors_dict[str(component_id)][error_type] = rtsm_error_summary
-                    else:
-                        rtsm_error_summary = component_errors_dict[str(component_id)][error_type]
-
-                    rtsm_error_summary[polarization] = dict(details=details, error_type=error_type,
-                                                            rcu_id=rcu_id,
-                                                            polarization=polarization, )
-
-            if component_name not in response_payload:
-                response_payload[component_name] = rtsm_errors_per_component_type
+                per_component_type[test_id] = dict(
+                    start_date=component_error.observation.start_datetime,
+                    end_date=component_error.observation.end_datetime,
+                    test_type='R',
+                    status=serialize_station_status(status.component_statuses, component_type),
+                    component_errors=dict()
+                )
+
+            per_test = per_component_type[test_id]['component_errors']
+
+            if component_id not in per_test:
+                per_test[component_id] = {error_type: {polarization: component_error_summary}}
             else:
-                response_payload[component_name] += rtsm_errors_per_component_type
+                per_test[component_id][error_type][polarization] = component_error_summary
 
+        for type in response_payload:
+            response_payload[type] = list(response_payload[type].values())
         return response_payload
 
     def compute_response(self):
@@ -954,23 +969,25 @@ class ControllerStationComponentErrors(ValidableReadOnlyView):
             station_test_errors = self.collect_station_test_errors()
 
         if self.test_type in ['R', 'B']:
-            rtsm_errors = {} # self.collect_rtsm_errors()
+            rtsm_errors = self.collect_rtsm_errors()
 
         payload = OrderedDict()
-        for component_type in set(rtsm_errors.keys() | station_test_errors.keys()):
+
+        for component_type in set(station_test_errors.keys() | rtsm_errors.keys()):
             station_test_errors_per_type = station_test_errors.get(component_type, [])
             rtsm_errors_per_type = rtsm_errors.get(component_type, [])
             payload[component_type] = OrderedDict()
             payload[component_type]['errors'] = station_test_errors_per_type
-            #payload[component_type]['errors'] = sorted(
-            #    station_test_errors_per_type + rtsm_errors_per_type,
-            #    key=lambda item: item['start_date'], reverse=True)
-
-            #payload[component_type]['current_status'] = \
-            #    latest_status_per_station_and_component_type(WinCCAntennaStatus.objects,
-            #                                                 self.station_name,
-            #                                                 component_type,
-            #                                                 to_date=datetime.datetime.now())
+
+            payload[component_type]['errors'] = sorted(
+                station_test_errors_per_type + rtsm_errors_per_type,
+                key=lambda item: item['start_date'], reverse=True)
+
+            payload[component_type]['current_status'] = \
+                latest_status_per_station_and_component_type(WinCCAntennaStatus.objects,
+                                                             self.station_name,
+                                                             component_type,
+                                                             to_date=datetime.datetime.now())
         return Response(status=status.HTTP_200_OK, data=payload)
 
 
@@ -1030,23 +1047,23 @@ class ControllerStationComponentElementErrors(ValidableReadOnlyView):
     ]
 
     def compute_ok_rtsm_list(self):
-        mode = COMPONENT_TO_MODE[self.component_type]
 
         rcus_per_polarization = rcus_from_antenna_and_type(self.antenna_id,
                                                            self.component_type)
 
-        good_observation_list = RTSMObservation.objects.filter(errors_summary__mode__in=mode,
-                                                               start_datetime__gt=self.from_date,
-                                                               end_datetime__lt=self.to_date,
-                                                               station__name=self.station_name). \
-            exclude(errors_summary__rcu__in=list(rcus_per_polarization.keys())). \
+        good_observation_list = RTSMObservation.objects.filter(
+            errors__component__type=self.component_type,
+            start_datetime__gt=self.from_date,
+            end_datetime__lt=self.to_date,
+            station__name=self.station_name). \
+            exclude(errors__rcu__in=list(rcus_per_polarization.keys())). \
             values('pk',
-                   'observation_id',
+                   'sas_id',
                    'start_datetime',
                    'end_datetime').distinct('pk')
         result = []
         for observation in good_observation_list:
-            entry = dict(test_id=observation['observation_id'],
+            entry = dict(test_id=observation['sas_id'],
                          db_id=observation['pk'],
                          start_date=observation['start_datetime'],
                          end_date=observation['end_datetime'],
@@ -1098,11 +1115,11 @@ class ControllerStationComponentElementErrors(ValidableReadOnlyView):
         rtsm_errors = RTSMError.objects.values('observation__pk',
                                                'observation__start_datetime',
                                                'observation__end_datetime',
-                                               'observation__observation_id',
+                                               'observation__sas_id',
                                                'observation__station__name',
                                                'pk',
                                                'rcu',
-                                               'mode',
+                                               'component__type',
                                                'error_type',
                                                'percentage',
                                                'count',
@@ -1110,13 +1127,13 @@ class ControllerStationComponentElementErrors(ValidableReadOnlyView):
             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],
+            component__type=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'],
+                errors[observation_pk] = dict(test_id=item['observation__sas_id'],
                                               db_id=item['observation__pk'],
                                               start_date=item[
                                                   'observation__start_datetime'],
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/views/logs_view.py b/LCU/Maintenance/DBInterface/monitoringdb/views/logs_view.py
index 51b961d3ac7806b7a317819ebac15f3974fcda40..4965ea61d3eaaabf111a1c219dc72c1acef46e39 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/views/logs_view.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/views/logs_view.py
@@ -1,5 +1,5 @@
 from rest_framework.viewsets import ReadOnlyModelViewSet
-from rest_framework.schemas import SchemaGenerator
+
 from lofar.maintenance.monitoringdb.serializers.log import ActionLogSerializer, ActionLog
 
 
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/views/rtsm_views.py b/LCU/Maintenance/DBInterface/monitoringdb/views/rtsm_views.py
index 1a7a78e3220c959c27e440ab06f6cae20f325803..c7f2ff1bf92a199853d893e4bdb4c11cc2dff04a 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/views/rtsm_views.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/views/rtsm_views.py
@@ -1,14 +1,18 @@
-from .common import *
-from ..models.rtsm import RTSMObservation
-from ..models.log import ActionLog
-from ..serializers.rtsm import RTSMObservationSerializer, RTSMErrorSerializer,\
-    RTSMSummaryPlotSerializer, RTSMError, RTSMErrorSample, RTSMErrorSampleSerializer
+import os
 from datetime import datetime
-from lofar.maintenance.monitoringdb.tasks.insert_raw_tests import insert_rtsm_test
-from django.shortcuts import get_object_or_404
+
+from .common import *
+from rest_framework import viewsets
 from django.http import Http404
-import os
+from django.shortcuts import get_object_or_404
+
+from lofar.maintenance.mdb.tests_validators import is_rtsm_test
 from lofar.maintenance.monitoringdb.tasks.generate_plots import check_error_summary_plot
+from lofar.maintenance.monitoringdb.tasks.insert_raw_tests import insert_rtsm_test
+from ..models.log import ActionLog
+from ..models.rtsm import RTSMObservation
+from ..serializers.rtsm import RTSMObservationSerializer, RTSMErrorSerializer, \
+    RTSMSummaryPlotSerializer, RTSMError, RTSMErrorSample, RTSMErrorSampleSerializer
 
 logger = logging.getLogger('views')
 
@@ -37,7 +41,7 @@ class RTSMSummaryPlot(viewsets.ViewSet):
     """
     queryset = RTSMError.objects.all()
 
-    def retrieve(self, request, pk=None):
+    def retrieve(self, _, pk=None):
         try:
             entity = get_object_or_404(self.queryset, pk=pk)
             summary_plot = entity.summary_plot.first()
@@ -47,22 +51,36 @@ class RTSMSummaryPlot(viewsets.ViewSet):
 
             uri = RTSMSummaryPlotSerializer(summary_plot).data['uri']
 
-        except ObjectDoesNotExist as e:
+        except ObjectDoesNotExist:
             check_error_summary_plot.delay(pk)
             raise Http404()
 
         if uri and os.path.exists(uri) and os.path.isfile(uri):
-                with open(uri, 'rb') as f_stream:
-                    image = f_stream.read()
-                return HttpResponse(image, status=status.HTTP_200_OK, content_type='image/gif')
+            with open(uri, 'rb') as f_stream:
+                image = f_stream.read()
+            return HttpResponse(image, status=status.HTTP_200_OK, content_type='image/gif')
         else:
             check_error_summary_plot.delay(pk)
-            print(uri)
+
             raise Http404()
 
 
+def handle_rtsm_insert(content, remote_addr):
+    action_log = ActionLog(who=remote_addr,
+                           what='INSERT_REQUESTED',
+                           when=datetime.now(),
+                           model_name='RTSM',
+                           entry_id=-1)
+
+    action_log.save()
+
+    insert_rtsm_test.delay(action_log.pk, content)
+
+    return action_log.pk
+
+
 @api_view(['POST'])
-def insert_raw_rtsm_test(request):
+def insert_raw_rtsm_test(request: HttpRequest):
     """
     This function is meant to parse a request of the form
     {
@@ -73,35 +91,31 @@ def insert_raw_rtsm_test(request):
     :return:
     """
     if request.method == 'POST':
+        remote_addr = request.META['REMOTE_ADDR']
+        logs_pk = []
 
         if 'content' in request.data:
-            try:
-                logger.info('handling raw request for %s', request)
-
-                logger.info('handling raw request for data %s', request.data)
-
-                action_log = ActionLog(who=request.META['REMOTE_ADDR'],
-                                       what='INSERT_REQUESTED',
-                                       when=datetime.now(),
-                                       model_name='StationTest',
-                                       entry_id=-1)
-
-                action_log.save()
-
-                insert_rtsm_test.delay(action_log.pk, request.data)
-
-                return Response(status=status.HTTP_200_OK,
-                                data='RTSM tests insert acknowledged.'
-                                     ' Check log for id %s' % action_log.pk)
-            except Exception as e:
-                logger.exception("exception occurred while parsing raw station info: %s", e)
-                return Response(exception=True,
-                                data="the post message is not correct." +
-                                     " It has to be of the form \{'content':[RAWSTRING]\}: %s. Request provided %s" % (
-                                         e, request),
-                                status=status.HTTP_400_BAD_REQUEST)
+            content = request.data
+
+            logs_pk += [handle_rtsm_insert(content, remote_addr)]
+        elif request.FILES:
+            files = request.FILES
+            for station_name, file in zip(request.data['station_name'], files.values()):
+                content_data = file.read().decode('UTF-8')
+                if not is_rtsm_test(content_data):
+                    return Response(exception=True,
+                                    data="the attached file is not a RTSM %s" % file.name,
+                                    status=status.HTTP_400_BAD_REQUEST)
+
+                content = dict(station_name=request.data['station_name'],
+                               content=content_data)
+                logs_pk += [handle_rtsm_insert(content, remote_addr)]
         else:
             return Response(exception=True,
                             data="the post message is not correct." +
                                  " It has to be of the form \{'content':[RAWSTRING]\}",
                             status=status.HTTP_400_BAD_REQUEST)
+
+        return Response(status=status.HTTP_200_OK,
+                        data='RTSM tests insert acknowledged.'
+                             ' Check log for id %s' % logs_pk)
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/views/station_test_views.py b/LCU/Maintenance/DBInterface/monitoringdb/views/station_test_views.py
index 07e8816d50e4deed8c95a2ea7927f650ffa6cd67..a3142310b088d68674f6bcaf7c036af2ead9df8c 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/views/station_test_views.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/views/station_test_views.py
@@ -1,11 +1,15 @@
+from datetime import datetime
+
 from django_filters import rest_framework as filters
 from rest_framework.viewsets import ReadOnlyModelViewSet
-
 from .common import *
+from lofar.maintenance.mdb.tests_validators import is_station_test
+from lofar.maintenance.monitoringdb.tasks.insert_raw_tests import insert_station_test
 from ..models.component import Component
 from ..models.component_error import ComponentError
 from ..models.element import Element
 from ..models.element_error import ElementError
+from ..models.log import ActionLog
 from ..models.station import Station
 from ..models.station_test import StationTest
 from ..serializers.component import ComponentSerializer
@@ -14,10 +18,9 @@ from ..serializers.element import ElementSerializer
 from ..serializers.element_error import ElementErrorSerializer
 from ..serializers.station import StationSerializer
 from ..serializers.station_tests import StationTestSerializer
-from ..models.log import ActionLog
-from datetime import datetime
-
-from lofar.maintenance.monitoringdb.tasks.insert_raw_tests import insert_station_test
+import logging
+from django.http import HttpRequest
+from rest_framework.decorators import api_view
 
 logger = logging.getLogger('views')
 
@@ -134,43 +137,52 @@ class ElementErrorViewSet(ReadOnlyModelViewSet):
     filterset_class = ElementErrorFilterSet
 
 
+def handle_stationtest_insert(content, remote_addr):
+    action_log = ActionLog(who=remote_addr,
+                           what='INSERT_REQUESTED',
+                           when=datetime.now(),
+                           model_name='STATIONTEST',
+                           entry_id=-1)
+
+    action_log.save()
+
+    insert_station_test.delay(action_log.pk, content)
+
+    return action_log.pk
+
+
 @api_view(['POST'])
-def insert_raw_station_test(request):
+def insert_raw_station_test(request: HttpRequest):
     """
     This function is meant to parse a request of the form
-    { content: [STATION TEST RAW TEXT]   }
-    parse the content field and create all the station_test entity related into the database
-
+    {
+    "content": "[STATION TEST RAW TEXT]"
+    }
+    :param request: HTTP request
+    :return:
     """
     if request.method == 'POST':
+        remote_addr = request.META['REMOTE_ADDR']
+        logs_pk = []
         if 'content' in request.data:
-            try:
-                logger.info('handling raw request for %s', request)
-                content = request.data['content']
-                logger.info('handling raw request for data %s', content)
-
-                action_log = ActionLog(who=request.META['REMOTE_ADDR'],
-                                       what='INSERT_REQUESTED',
-                                       when=datetime.now(),
-                                       model_name='StationTest',
-                                       entry_id=-1)
-
-                action_log.save()
-
-                insert_station_test.delay(action_log.pk, content)
-
-                return Response(status=status.HTTP_200_OK,
-                                data='Station tests insert acknowledged.'
-                                     ' Check log for id %s' % action_log.pk)
-            except Exception as e:
-                logger.exception("exception occurred while parsing raw station info: %s", e)
-                return Response(exception=True,
-                                data="the post message is not correct." +
-                                     " It has to be of the form \{'content':[RAWSTRING]\}: %s. Request provided %s" % (
-                                         e, request),
-                                status=status.HTTP_400_BAD_REQUEST)
+            content = request.data
+            logs_pk += [handle_stationtest_insert(content, remote_addr)]
+        elif request.FILES:
+            files = request.FILES
+            for file in files.values():
+                content_data = file.read().decode('UTF-8')
+                if not is_station_test(content_data):
+                    return Response(exception=True,
+                                    data="the attached file is not a station test %s" % file.name,
+                                    status=status.HTTP_400_BAD_REQUEST)
+
+                logs_pk += [handle_stationtest_insert(content_data, remote_addr)]
         else:
             return Response(exception=True,
                             data="the post message is not correct." +
                                  " It has to be of the form \{'content':[RAWSTRING]\}",
                             status=status.HTTP_400_BAD_REQUEST)
+
+        return Response(status=status.HTTP_200_OK,
+                        data='station test insert acknowledged.'
+                             ' Check log for id %s' % logs_pk)
diff --git a/LCU/Maintenance/Docker/BaseMaintenance/Dockerfile b/LCU/Maintenance/Docker/BaseMaintenance/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..48ad26b13eba2945f6c505c092cdbbf2bffc7228
--- /dev/null
+++ b/LCU/Maintenance/Docker/BaseMaintenance/Dockerfile
@@ -0,0 +1,14 @@
+FROM centos:7.6.1810
+
+RUN yum install -y epel-release && \
+    yum install -y python36 python36-psycopg2 python36-pip && \
+    yum clean all
+RUN pip3 install beautifultable==0.7.0 \
+                 blessings==1.7 \
+                 celery==4.3.0 \
+                 django==2.2 \
+                 djangorestframework==3.9.4 \
+                 django-coreapi==2.3 \
+                 django-filter==2.1 \
+                 inotify==0.2.10 \
+                 requests==2.22
diff --git a/LCU/Maintenance/Docker/DBInterface/Dockerfile b/LCU/Maintenance/Docker/DBInterface/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..13f7e8865aa820117ea6a4a7de28efaf8549d7bf
--- /dev/null
+++ b/LCU/Maintenance/Docker/DBInterface/Dockerfile
@@ -0,0 +1,16 @@
+FROM lofar-maintenance-base as lofar-build-dbinterface
+
+ARG SRC_DIR=/root/src
+ARG BUILD_DIR=$SRC_DIR/build/gnucxx11_debug
+
+RUN yum install -y cmake>3 make gcc-c++ gcc python36-devel
+COPY . $SRC_DIR
+RUN mkdir -p $BUILD_DIR
+WORKDIR $BUILD_DIR
+RUN cmake $SRC_DIR -DBUILD_PACKAGES=DBInterface \
+                   -DCMAKE_INSTALL_PREFIX=/opt/lofar \
+                   -DUSE_LOG4CPLUS=OFF && \
+    make && make install
+
+FROM lofar-maintenance-base
+COPY --from=lofar-build-dbinterface /opt/lofar/ /opt/lofar
\ No newline at end of file
diff --git a/LCU/Maintenance/Docker/RESTService/Dockerfile b/LCU/Maintenance/Docker/RESTService/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..f7e6dde09870a013533af388424b2f8f15d40743
--- /dev/null
+++ b/LCU/Maintenance/Docker/RESTService/Dockerfile
@@ -0,0 +1,14 @@
+FROM lofar-maintenance-dbinterface
+
+
+RUN pip3.6 install gunicorn==19.9.0 \
+                   gevent \
+                   django-silk==3.0.2
+
+COPY LCU/Maintenance/Docker/RESTService/entrypoint.sh /root/
+
+SHELL ["/bin/bash"]
+
+ENTRYPOINT /root/entrypoint.sh
+
+ENV N_WORKERS=1
\ No newline at end of file
diff --git a/LCU/Maintenance/Docker/RESTService/entrypoint.sh b/LCU/Maintenance/Docker/RESTService/entrypoint.sh
new file mode 100755
index 0000000000000000000000000000000000000000..7fc58b8f2727ec845e0cc3d797456a00248aaf3e
--- /dev/null
+++ b/LCU/Maintenance/Docker/RESTService/entrypoint.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+
+source /opt/lofar/lofarinit.sh
+gunicorn -w $N_WORKERS -k gevent lofar.maintenance.django_postgresql.wsgi:application $@
\ No newline at end of file
diff --git a/LCU/Maintenance/Docker/docker-compose.yml b/LCU/Maintenance/Docker/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ebf033f3c02714bbfc25f75fc63b908d2de9e344
--- /dev/null
+++ b/LCU/Maintenance/Docker/docker-compose.yml
@@ -0,0 +1,13 @@
+version: "3.0"
+
+services:
+  lofar-maintenance-base:
+    image: lofar-maintenance-base
+    build:
+      context: ../../../
+      dockerfile: LCU/Maintenance/Docker/BaseMaintenance/Dockerfile
+  lofar-maintenance-dbinterface:
+    image: lofar-maintenance-dbinterface
+    build:
+      context: ../../../
+      dockerfile: LCU/Maintenance/Docker/DBInterface/Dockerfile
\ No newline at end of file
diff --git a/LCU/Maintenance/MDB_tools/cli/station_tests_watchdog.py b/LCU/Maintenance/MDB_tools/cli/station_tests_watchdog.py
index d4e7d6757761f810981ebd1a93a0cf63197c93e7..a8c54f8dfe7bb84746673e904b929570f2d7bfe6 100644
--- a/LCU/Maintenance/MDB_tools/cli/station_tests_watchdog.py
+++ b/LCU/Maintenance/MDB_tools/cli/station_tests_watchdog.py
@@ -145,7 +145,7 @@ def _is_file_created(event):
     :param event: the list that describes the creation event
     :return: True if it refers to a file being created False otherwise
     """
-    return 'IN_CLOSE_WRITE' in event[1]
+    return 'IN_CLOSE_WRITE' in event[1] or 'IN_MOVED_TO' in event[1] or 'IN_CREATE' in event[1]
 
 
 def handle_event_file_created(mdb_address, event):
diff --git a/LCU/Maintenance/MDB_tools/lib/CMakeLists.txt b/LCU/Maintenance/MDB_tools/lib/CMakeLists.txt
index e7929148818f5d9388dc39b42607b0b8a1e1c3a3..725c506de7545a0972f136d58849093f8d4b7cba 100644
--- a/LCU/Maintenance/MDB_tools/lib/CMakeLists.txt
+++ b/LCU/Maintenance/MDB_tools/lib/CMakeLists.txt
@@ -4,6 +4,7 @@ find_python_module(requests)
 set(_py_files
     __init__.py
     client.py
+    tests_validators.py
   )
 
 python_install(${_py_files} DESTINATION lofar/maintenance/mdb)
diff --git a/LCU/Maintenance/MDB_tools/lib/client.py b/LCU/Maintenance/MDB_tools/lib/client.py
index b84538036130a1e1bab354cf0c031efdc1958d78..b4621ed1049a785c784f3fc377bdf3cc20e608f0 100644
--- a/LCU/Maintenance/MDB_tools/lib/client.py
+++ b/LCU/Maintenance/MDB_tools/lib/client.py
@@ -1,7 +1,7 @@
 import logging
 import requests
 import re
-
+from .tests_validators import is_rtsm_test, is_station_test
 logger = logging.getLogger(__name__)
 
 
@@ -21,7 +21,7 @@ def read_stationtest_rtsm_output_to_dict(file_path):
     try:
         with open(file_path, 'r') as input_stream:
             rtsm_filename_pattern = '(?P<station_name>\w*)_\d+_\d+\.dat$'
-            result = dict(content=input_stream.read().strip('\n'))
+            result = dict(content=input_stream.read().strip('\n'), file_path=file_path)
             if re.search(rtsm_filename_pattern, file_path):  # Matches the filename to understand if it is a RTSM
                 result.update(station_name=re.search(rtsm_filename_pattern, file_path).group('station_name'))
             return result
@@ -31,84 +31,6 @@ def read_stationtest_rtsm_output_to_dict(file_path):
         return None
 
 
-def is_station_test(content):
-    """
-    Check if the content of the file have the structure of a station test.
-    Example content
-20130522,NFO,---,STATION,NAME=CS011C
-20130522,NFO,---,RUNTIME,START=03:10:00,STOP=05:52:27
-20130522,NFO,---,CHECKS,S1,O1,SP1,NS1=180,S3,O3,SP3,NS3=180,M,O5,SN,SP5,NS5=300,EO,ESP,EN=30,TV,TM
-20130522,NFO,---,STATISTICS,BAD_LBL=1,BAD_LBH=2,BAD_HBA=7
-20130522,LBL,---,TESTSIGNAL,SUBBANDX=301,SIGNALX=82.0,SUBBANDY=301,SIGNALY=81.8
-20130522,LBL,019,RF_FAIL,X=77.0,Y=70.1
-20130522,LBL,019,LOW_NOISE,Xproc=100.000,Xval=66.6,Xdiff=0.0,Xref=67.7
-20130522,LBH,---,TESTSIGNAL,SUBBANDX=301,SIGNALX=81.8,SUBBANDY=301,SIGNALY=82.2
-20130522,LBH,013,RF_FAIL,Y=72.9
-20130522,LBH,013,LOW_NOISE,Yproc=100.000,Yval=66.0,Ydiff=0.1,Yref=67.8
-20130522,LBH,015,RF_FAIL,X=68.0
-20130522,LBH,015,LOW_NOISE,Xproc=100.000,Xval=67.8,Xdiff=0.0,Xref=68.0
-20130522,HBA,003,E_FAIL,HNX13=64.3 2.3,JX13=1.0
-20130522,HBA,009,E_FAIL,HNX8=62.7 0.9
-20130522,HBA,010,HIGH_NOISE,Xproc=1.333,Xval=73.3,Xdiff=3.9,Xref=70.8,Yproc=0.500,Yval=71.5,Ydiff=3.1,Yref=70.9
-20130522,HBA,010,JITTER,Xproc=92.278,Xdiff=3.9,Xref=0.1,Yproc=99.611,Ydiff=3.1,Yref=0.2
-20130522,HBA,010,E_FAIL,HNY3=63.7 2.5,JY3=1.0,HNY12=64.0 2.5,JY12=1.0
-20130522,HBA,015,HIGH_NOISE,Xproc=8.278,Xval=74.2,Xdiff=5.6,Xref=70.8
-20130522,HBA,015,JITTER,Xproc=98.500,Xdiff=5.6,Xref=0.1
-20130522,HBA,015,SUMMATOR_NOISE,Y=1
-20130522,HBA,015,E_FAIL,HNX12=66.4 5.5,JX12=1.0
-20130522,HBA,019,JITTER,Xproc=99.333,Xdiff=1.9,Xref=0.1
-20130522,HBA,026,RF_FAIL,X=106.4 357 121.0 107.8 357 121.2
-20130522,HBA,036,SUMMATOR_NOISE,X=1,Y=1
-20130522,HBA,036,E_FAIL,SPX9=1,SPY9=1,SPX10=1,SPY10=1,SPX13=1,SPY13=1,SPX14=1,SPY14=1
-    :param content: list of strings representing the content of the file
-    :return: True if the content refers to a RTSM and it is not empty
-    """
-    if content:
-        pattern = r'(^#.*)|(^\d*,[A-z]{3},[\d-]{3})'
-        number_of_lines = len(content.splitlines())
-        pattern_matching_lines = re.findall(pattern, content, re.MULTILINE)
-        number_pattern_matching_lines = len(pattern_matching_lines)
-        logger.debug('Station Test: number of matched lines %d/%d number of lines in files',
-                     number_pattern_matching_lines,
-                     number_of_lines)
-
-        return number_of_lines == number_pattern_matching_lines
-    else:
-        return False
-
-
-def is_rtsm_test(content):
-    """
-    Check if the content of the file have the structure of a RTSM
-    Example content of a valid RTSM test
-# SPECTRA-INFO=rcu,rcumode,obs-id,check,startfreq,stopfreq,rec-timestamp
-#
-SPECTRA-INFO=4,5,641999,SN,100,200,1520208664.423739
-MEAN-SPECTRA=[0.0 60.0 60.0 60.0 60.0 60.0 60.0 60.0 60.1 60.0 60.0 60.1 60.1 60.1 60.2 60.1 60.0 60.1 60.0 60.9 60.7 60.1 60.9 60.0 60.3 60.1 60.0 61.5 60.1 61.0 62.9 73.4 60.9 64.0 61.7 62.4 70.3 64.5 62.4 72.7 68.6 64.0 61.8 62.0 62.3 62.5 62.7 62.9 63.1 63.5 63.7 63.9 64.0 64.1 64.3 64.6 64.6 64.6 64.8 64.9 65.1 65.2 65.3 65.3 65.3 65.5 65.6 65.7 65.7 65.8 66.0 66.2 66.3 66.3 66.4 66.6 66.8 66.8 66.9 66.9 66.9 67.1 67.2 67.2 67.2 67.2 67.4 67.5 67.6 67.5 67.6 67.7 67.9 68.0 68.0 68.0 68.1 68.2 68.3 68.4 68.4 68.4 68.6 68.7 68.7 68.7 68.6 68.7 68.8 68.9 68.8 68.8 68.9 68.9 69.0 69.0 69.0 69.0 69.0 69.0 69.0 69.0 69.0 69.0 69.1 69.1 69.1 69.0 69.1 69.1 69.2 69.2 69.1 69.1 69.6 69.2 69.2 69.1 69.1 69.1 69.1 69.1 69.0 69.0 69.0 68.9 69.0 68.9 68.9 68.8 68.8 68.9 68.9 68.8 68.7 68.7 68.8 68.9 68.8 68.7 68.8 68.8 68.8 68.8 68.7 68.7 68.7 68.8 68.7 68.7 68.6 68.7 68.8 68.7 68.7 68.5 68.6 68.7 68.7 68.7 68.6 68.5 68.6 68.6 68.6 68.5 68.4 68.5 68.5 68.4 68.4 76.1 71.0 68.4 68.4 68.4 68.3 68.3 68.3 68.3 68.3 68.2 68.2 68.2 68.2 68.3 68.1 68.0 68.0 67.9 67.9 67.9 67.9 67.8 67.8 67.8 67.8 67.8 67.7 67.6 67.7 67.8 67.7 67.6 67.6 67.6 67.7 67.7 67.6 67.5 67.5 67.6 67.5 67.5 67.4 67.4 67.5 67.5 67.5 67.3 67.3 67.5 67.5 67.5 67.4 67.4 67.4 67.5 67.4 67.4 67.3 67.3 67.4 67.4 67.3 67.3 67.5 67.3 67.4 67.3 67.3 67.2 67.3 67.3 67.3 67.2 67.2 67.2 67.3 67.3 67.3 67.3 67.2 67.3 67.6 67.3 67.4 67.4 67.5 67.5 67.5 67.5 67.6 67.6 67.7 67.7 67.7 67.8 67.8 67.8 67.8 67.8 67.8 67.8 67.8 67.8 67.8 67.8 67.9 67.9 67.9 67.9 67.9 68.0 68.0 68.0 68.1 68.2 68.2 68.2 68.2 68.2 68.3 68.4 68.3 68.3 68.3 68.4 68.4 68.4 68.2 68.1 68.2 68.2 68.2 68.1 68.1 68.1 68.1 68.1 68.1 68.0 68.0 68.1 68.1 68.0 67.9 67.8 67.9 67.9 67.9 67.8 67.8 67.8 67.9 67.9 67.8 67.7 67.7 67.8 67.8 67.7 67.7 67.6 67.7 67.7 67.6 67.6 67.7 67.6 67.6 67.5 67.5 67.4 67.4 67.4 67.4 67.4 67.4 67.3 67.4 67.4 67.4 67.4 67.4 67.3 67.4 67.3 67.3 67.3 67.3 67.3 67.3 67.3 67.3 67.2 67.2 67.2 67.2 67.2 67.1 67.1 67.1 67.0 67.0 66.9 66.9 79.8 85.4 85.4 85.2 85.7 85.4 85.1 85.9 83.0 66.3 66.2 66.2 66.2 66.1 66.0 65.9 65.9 65.9 65.8 65.7 65.6 65.6 65.6 65.5 65.4 65.3 65.3 83.8 92.0 91.5 91.0 90.5 90.9 90.3 90.5 88.3 65.0 64.7 64.7 64.6 64.5 64.4 64.4 64.3 64.2 64.1 64.0 63.9 63.8 63.7 63.5 63.4 63.5 63.4 63.3 63.1 63.0 62.8 62.7 62.6 62.5 62.3 62.2 62.1 62.2 61.9 61.8 61.6 61.6 61.5 61.4 61.3 68.2 69.7 69.5 69.5 68.8 68.5 68.9 67.8 64.1 60.9 60.9 60.9 60.8 60.9 60.8 60.8 60.7 60.6 60.6 60.5 60.4 60.3 60.2 60.2 60.1 60.1 60.0 60.0 60.1 60.0 60.0 60.0 60.0 60.0 60.0 60.0 60.0 60.0 60.0 60.0 60.1 60.4 60.3 ]
-BAD-SPECTRA=[0.0 59.7 59.7 59.7 59.7 59.7 59.7 59.7 59.8 59.8 59.8 59.8 59.8 59.8 60.3 59.8 59.8 59.8 59.8 63.1 62.5 59.8 61.3 59.9 60.2 59.9 59.8 63.6 59.9 61.0 60.6 60.4 61.8 68.8 64.5 62.4 72.1 66.5 63.5 72.9 71.5 66.2 62.7 64.2 64.6 64.1 66.0 66.1 66.1 66.9 68.2 68.4 67.4 69.5 69.3 68.2 70.3 69.7 69.7 69.1 70.7 70.6 69.3 71.2 70.6 69.6 71.1 70.5 70.6 69.3 70.9 70.9 69.6 71.4 71.0 70.4 71.0 71.3 70.8 69.2 70.4 69.8 68.8 70.1 69.9 69.8 69.9 71.1 71.0 69.9 71.6 71.2 70.3 71.7 71.1 70.9 70.2 71.1 70.8 69.8 70.8 70.4 70.1 70.6 70.5 70.4 69.7 70.6 70.5 69.8 70.5 70.2 69.8 70.0 70.3 70.1 69.5 70.2 70.0 69.6 70.2 69.9 69.7 69.6 70.1 69.9 69.5 69.9 69.8 69.6 70.0 69.7 69.6 69.4 69.9 69.7 69.5 69.8 69.6 69.6 69.9 69.8 69.9 69.5 70.0 70.0 69.7 70.3 70.1 70.0 70.1 70.6 70.6 69.9 70.8 70.6 70.0 70.9 70.6 70.3 69.9 70.5 70.2 69.6 70.0 69.6 69.3 69.7 69.5 69.3 69.0 69.2 69.2 68.9 68.9 68.7 68.6 68.8 68.8 68.6 68.5 68.5 68.6 68.6 68.6 68.5 68.4 68.5 68.6 68.5 68.4 79.2 73.0 68.4 68.5 68.4 68.3 68.3 68.5 68.4 68.4 68.6 68.5 68.4 68.6 68.6 68.5 68.3 68.5 68.4 68.2 68.6 68.4 68.2 68.3 68.4 68.4 68.1 68.3 68.2 67.9 68.2 68.0 67.8 67.7 67.7 67.7 67.6 67.6 67.4 67.3 67.5 67.5 67.3 67.2 67.3 67.3 67.3 67.2 67.1 67.1 67.2 67.3 67.2 67.1 67.1 67.1 67.2 67.2 67.1 67.0 67.1 67.1 67.2 67.0 67.0 67.3 67.1 67.2 67.1 67.0 67.0 67.1 67.2 67.1 67.1 67.1 67.1 67.3 67.2 67.1 67.2 67.2 67.3 67.2 67.3 67.3 67.3 67.5 67.4 67.4 67.5 67.5 67.6 67.6 67.6 67.8 67.7 67.8 67.9 67.8 67.9 67.8 67.8 67.8 67.7 67.8 67.7 67.7 67.9 67.7 67.7 67.8 67.8 67.9 67.9 67.9 68.0 68.0 68.1 68.0 68.0 68.3 68.2 68.3 68.2 68.3 68.5 68.4 68.4 68.2 68.1 68.2 68.1 68.1 68.0 67.9 68.0 67.9 67.9 67.9 67.8 67.9 67.9 67.8 67.8 67.7 67.6 67.7 67.7 67.7 67.6 67.6 67.6 67.6 67.6 67.5 67.4 67.4 67.5 67.5 67.4 67.4 67.3 67.4 67.4 67.3 67.3 67.5 67.3 67.3 67.3 67.3 67.2 67.2 67.2 67.3 67.2 67.2 67.2 67.2 67.2 67.3 67.2 67.3 67.2 67.3 67.2 67.2 67.2 67.2 67.2 67.2 67.2 67.2 67.1 67.1 67.1 67.1 67.0 67.0 67.0 67.0 67.0 66.9 66.9 66.9 84.6 90.8 91.0 91.1 90.9 91.4 91.4 91.2 89.1 66.3 66.3 66.3 66.2 66.1 66.0 66.0 66.0 66.0 65.9 65.8 65.7 65.7 65.7 65.6 65.5 65.4 65.4 84.9 90.8 91.5 90.2 90.7 90.9 90.1 90.5 88.6 65.0 64.8 64.8 64.7 64.6 64.5 64.4 64.4 64.3 64.1 64.0 63.9 63.8 63.7 63.5 63.4 63.5 63.4 63.3 63.1 62.9 62.8 62.6 62.6 62.4 62.3 62.1 62.0 62.0 61.8 61.8 61.6 61.5 61.4 61.3 61.3 64.0 65.2 65.9 65.6 66.5 67.0 66.9 67.1 62.9 60.9 60.9 60.9 60.8 60.8 60.8 60.7 60.7 60.6 60.6 60.5 60.4 60.3 60.2 60.1 60.0 60.0 59.9 59.9 59.9 59.9 59.9 59.9 59.9 59.9 59.9 59.8 59.9 59.9 59.8 59.9 60.1 60.2 60.3 ]
-
-SPECTRA-INFO=4,5,641999,SN,100,200,1520208724.588988
-MEAN-SPECTRA=[0.0 59.8 59.8 59.8 59.8 59.8 59.8 59.8 59.8 59.8 59.8 59.8 59.8 59.8 59.9 59.8 59.7 59.7 60.3 60.9 60.3 59.8 60.7 59.7 60.0 59.7 59.7 61.1 59.9 60.6 62.7 73.3 60.6 63.7 61.4 62.3 70.5 64.5 62.3 72.8 68.4 64.5 61.7 61.9 62.3 62.4 64.5 62.8 63.0 63.4 63.6 63.8 63.9 64.0 64.3 64.5 64.6 64.7 64.8 64.9 65.2 65.3 65.4 65.4 65.4 65.5 65.7 65.7 65.7 65.8 66.0 66.2 66.3 66.3 66.4 66.4 66.7 66.8 66.8 66.8 66.8 67.1 67.2 67.2 67.2 67.2 67.4 67.5 67.6 67.6 67.7 67.8 68.0 68.1 68.1 68.0 68.2 68.3 68.4 68.4 68.3 68.3 68.6 68.6 68.6 68.6 68.6 68.7 68.8 69.0 69.2 68.8 68.8 68.9 69.0 69.0 69.0 69.0 69.0 69.1 69.2 69.1 69.1 69.1 69.2 69.2 69.2 69.1 69.2 69.2 69.2 69.2 69.2 69.1 69.6 69.2 69.2 69.2 69.1 69.1 69.1 69.1 69.0 68.9 68.9 68.9 69.0 69.0 68.9 68.8 68.9 68.9 69.0 68.9 68.8 68.8 68.9 68.9 68.9 68.8 68.8 68.8 68.9 68.9 68.7 68.7 68.7 68.8 68.7 68.6 68.6 68.6 68.8 68.7 68.6 68.5 68.5 68.7 68.7 68.6 68.5 68.5 68.6 68.5 68.6 68.4 68.4 68.5 68.5 68.4 68.4 76.0 71.1 68.5 68.5 68.4 68.3 68.3 68.3 68.3 68.3 72.2 68.2 68.2 68.2 68.3 68.1 68.1 68.0 67.9 68.0 67.9 67.9 67.8 67.8 67.8 67.8 67.7 67.7 67.7 67.7 67.7 67.7 67.6 67.6 67.5 67.6 67.6 67.5 67.4 67.5 67.5 67.5 67.4 67.3 67.3 67.4 67.5 67.3 67.3 67.2 67.5 67.4 67.4 67.3 67.2 67.3 67.3 67.4 67.3 67.2 67.2 67.3 67.4 67.3 67.3 67.5 67.3 67.4 67.3 67.3 67.2 67.3 67.4 67.4 67.9 67.3 67.4 67.5 67.4 67.4 67.4 67.4 67.4 67.5 67.3 67.5 67.4 67.5 67.5 67.5 67.5 67.5 67.5 67.6 67.6 67.6 67.7 67.7 67.7 67.7 67.7 67.7 68.1 67.8 67.7 67.8 67.8 67.9 67.9 67.9 67.9 67.9 68.0 68.0 68.1 68.1 68.2 68.2 68.2 68.1 68.1 68.2 68.3 68.3 68.2 68.2 68.3 68.3 68.3 68.1 68.1 68.1 68.2 68.2 68.1 68.1 68.1 68.2 68.2 68.1 68.0 68.0 68.1 68.1 68.0 67.9 67.8 67.9 67.9 67.9 67.8 67.8 67.8 67.8 67.8 67.8 67.7 67.7 67.7 67.8 67.7 67.7 67.7 67.7 67.8 91.2 125.0 67.8 67.6 67.6 67.6 67.5 67.4 67.4 67.4 67.4 67.4 67.4 67.4 67.4 67.5 67.5 67.4 67.4 67.4 67.5 67.4 67.4 67.4 67.4 67.4 67.4 67.4 67.3 67.3 67.3 67.3 67.2 67.1 67.1 67.1 67.1 67.0 67.0 66.9 66.9 80.0 85.6 85.5 85.5 85.7 85.6 85.3 85.8 83.1 66.3 66.3 66.3 66.2 66.2 66.0 66.0 66.0 65.9 65.8 65.8 65.7 65.6 65.6 65.9 65.4 65.3 65.3 83.5 91.8 91.4 91.0 90.4 90.8 90.1 90.2 88.2 65.0 64.7 64.7 64.6 64.5 64.5 64.4 64.4 64.3 64.1 64.0 63.9 63.8 63.7 63.5 63.6 63.5 63.4 63.2 63.1 62.9 62.8 62.7 62.6 62.4 62.3 62.1 62.1 62.1 61.9 61.8 61.6 61.5 61.4 61.4 61.3 68.3 69.9 69.7 69.7 69.0 68.6 68.8 67.9 64.1 60.9 60.8 60.8 60.7 60.7 60.7 60.7 60.5 60.5 60.4 60.4 60.3 60.2 60.2 60.1 60.0 60.0 60.0 59.9 60.0 60.0 60.0 59.9 59.9 59.9 59.9 59.9 59.9 59.8 59.8 59.8 59.8 59.9 60.0 ]
-BAD-SPECTRA=[0.0 59.6 59.6 59.6 59.6 59.6 59.6 59.6 59.6 59.6 59.6 59.6 59.6 59.6 60.1 59.6 59.5 59.5 60.5 63.3 61.7 59.5 61.0 59.6 60.0 59.6 59.5 63.4 59.6 60.9 60.4 60.0 61.6 68.9 64.1 62.3 72.0 66.4 63.4 73.0 71.4 67.0 62.7 64.2 64.7 64.0 67.6 66.1 66.2 66.8 68.3 68.4 67.3 69.5 69.3 68.2 70.3 69.7 69.8 69.1 70.8 70.7 69.3 71.3 70.7 69.7 71.1 70.7 70.6 69.3 71.0 70.9 69.6 71.4 71.0 70.3 70.9 71.3 70.8 69.2 70.4 69.8 68.8 70.2 70.0 69.9 70.0 71.2 71.1 70.0 71.7 71.3 70.4 71.7 71.1 70.9 70.1 71.0 70.7 69.7 70.7 70.2 69.9 70.5 70.4 70.3 69.6 70.6 70.5 69.9 70.7 70.2 69.9 70.0 70.4 70.2 69.6 70.3 70.1 69.7 70.4 70.0 69.9 69.8 70.2 70.0 69.6 70.0 69.8 69.6 70.0 69.7 69.6 69.4 69.9 69.6 69.5 69.7 69.5 69.5 69.8 69.7 69.8 69.4 70.0 70.0 69.6 70.3 70.1 70.0 70.1 70.6 70.7 70.0 70.9 70.6 70.1 71.0 70.7 70.4 70.0 70.6 70.2 69.6 70.1 69.7 69.4 69.7 69.5 69.3 68.9 69.2 69.1 68.8 68.9 68.6 68.6 68.7 68.6 68.5 68.4 68.4 68.5 68.5 68.5 68.4 68.4 68.4 68.6 68.5 68.4 75.4 70.5 68.4 68.6 68.5 68.4 68.4 68.6 68.5 68.4 70.5 68.5 68.5 68.7 68.6 68.6 68.3 68.6 68.5 68.3 68.6 68.4 68.2 68.3 68.4 68.4 68.1 68.3 68.1 67.9 68.1 68.0 67.7 67.6 67.7 67.6 67.5 67.5 67.3 67.3 67.4 67.4 67.2 67.1 67.1 67.2 67.3 67.1 67.0 67.0 67.2 67.2 67.1 67.0 67.0 67.1 67.2 67.2 67.1 67.1 67.1 67.2 67.2 67.1 67.1 67.4 67.2 67.3 67.1 67.1 67.1 67.2 67.3 67.1 67.4 67.2 67.2 67.3 67.3 67.2 67.2 67.2 67.3 67.3 67.3 67.3 67.3 67.5 67.4 67.4 67.4 67.4 67.5 67.4 67.5 67.6 67.5 67.7 67.6 67.6 67.6 67.6 67.8 67.7 67.7 67.8 67.7 67.8 67.9 67.8 67.8 67.9 67.9 68.0 67.9 68.0 68.0 68.0 68.2 68.0 67.9 68.2 68.1 68.2 68.0 68.0 68.2 68.1 68.2 67.9 67.9 68.0 68.0 68.0 67.9 67.9 67.9 68.0 68.0 67.9 67.8 67.9 67.9 67.9 67.9 67.7 67.7 67.7 67.7 67.7 67.6 67.5 67.6 67.6 67.6 67.5 67.4 67.4 67.5 67.5 67.4 67.5 67.4 67.5 67.5 94.6 128.3 67.6 67.4 67.4 67.3 67.3 67.2 67.2 67.2 67.2 67.2 67.2 67.2 67.2 67.2 67.3 67.2 67.2 67.2 67.3 67.3 67.3 67.2 67.2 67.2 67.2 67.2 67.2 67.2 67.1 67.1 67.1 67.1 67.0 67.0 67.0 67.0 67.0 66.9 66.9 85.0 90.9 91.1 91.3 91.2 91.7 91.7 91.4 89.3 66.4 66.3 66.3 66.3 66.2 66.0 66.0 66.0 66.0 65.9 65.9 65.7 65.7 65.7 66.3 65.6 65.4 65.4 84.9 90.9 91.4 90.5 90.6 91.1 90.1 90.6 88.4 65.0 64.8 64.8 64.7 64.6 64.5 64.4 64.4 64.3 64.1 64.0 63.9 63.8 63.7 63.6 64.2 63.5 63.4 63.3 63.1 62.9 62.8 62.7 62.6 62.4 62.3 62.1 62.1 62.0 61.9 61.7 61.6 61.5 61.4 61.4 61.3 64.0 65.2 65.8 65.6 66.4 66.9 66.8 67.0 62.8 60.8 60.8 60.8 60.7 60.7 60.6 60.6 60.5 60.5 60.4 60.3 60.2 60.2 60.1 60.0 59.9 59.9 59.8 59.8 59.8 59.8 59.8 59.8 59.7 59.7 59.7 59.7 59.7 59.7 59.7 59.7 59.7 59.8 59.9 ]
-
-# OBS-ID-INFO=obsid,start_time,stop_time,obsid_samples
-OBS-ID-INFO=641999,1520208240.000,1520211840.000,59
-    :param content: list of strings representing the content of the file
-    :return: True if the content refers to a RTSM and it is not empty
-    """
-    if content:
-        pattern = r'(^#.*)|(^\w*-\w*=)|(^\w*-\w*-\w*=)|(^$)'
-        number_of_lines = len(content.splitlines())
-        pattern_matching_lines = re.findall(pattern, content, re.MULTILINE)
-        number_pattern_matching_lines = len(pattern_matching_lines)
-        logger.debug('RTSM: number of matched lines %d/%d number of lines in files',
-                     number_pattern_matching_lines,
-                     number_of_lines)
-        return number_of_lines == number_pattern_matching_lines
-    else:
-        return False
-
-
 def send_stationtest_rtsm_content_to_address(address, content):
     """
     Send the stationtest RTSM content to the web site API at the given address
@@ -117,10 +39,13 @@ def send_stationtest_rtsm_content_to_address(address, content):
     :return: True if the request was successful False otherwise
     """
     full_address = '/'.join([address, compose_api_url_for_given_test_type(content['content'])])
+    file_path = content.pop('file_path')
+    content.pop('content')
     logger.info('performing request to address %s', full_address)
 
     logger.debug('request content %s', content)
-    response = requests.post(full_address, data=content)
+    with open(file_path, 'rb') as file_stream:
+        response = requests.post(full_address, data=content, files=dict(upload_file=file_stream))
     logger.info('response acknowledged: status code is %s, reason %s, content %s', response.status_code,
                  response.reason,
                  response.content)
diff --git a/LCU/Maintenance/MDB_tools/lib/tests_validators.py b/LCU/Maintenance/MDB_tools/lib/tests_validators.py
new file mode 100644
index 0000000000000000000000000000000000000000..ef5271e377f1fbe7f0ae7a8a07f36cef713d5fa8
--- /dev/null
+++ b/LCU/Maintenance/MDB_tools/lib/tests_validators.py
@@ -0,0 +1,82 @@
+import logging
+import re
+
+logger = logging.getLogger(__name__)
+
+
+def is_rtsm_test(content):
+    """
+    Check if the content of the file have the structure of a RTSM
+    Example content of a valid RTSM test
+# SPECTRA-INFO=rcu,rcumode,obs-id,check,startfreq,stopfreq,rec-timestamp
+#
+SPECTRA-INFO=4,5,641999,SN,100,200,1520208664.423739
+MEAN-SPECTRA=[0.0 60.0 60.0 60.0 60.0 60.0 60.0 60.0 60.1 60.0 60.0 60.1 60.1 60.1 60.2 60.1 60.0 60.1 60.0 60.9 60.7 60.1 60.9 60.0 60.3 60.1 60.0 61.5 60.1 61.0 62.9 73.4 60.9 64.0 61.7 62.4 70.3 64.5 62.4 72.7 68.6 64.0 61.8 62.0 62.3 62.5 62.7 62.9 63.1 63.5 63.7 63.9 64.0 64.1 64.3 64.6 64.6 64.6 64.8 64.9 65.1 65.2 65.3 65.3 65.3 65.5 65.6 65.7 65.7 65.8 66.0 66.2 66.3 66.3 66.4 66.6 66.8 66.8 66.9 66.9 66.9 67.1 67.2 67.2 67.2 67.2 67.4 67.5 67.6 67.5 67.6 67.7 67.9 68.0 68.0 68.0 68.1 68.2 68.3 68.4 68.4 68.4 68.6 68.7 68.7 68.7 68.6 68.7 68.8 68.9 68.8 68.8 68.9 68.9 69.0 69.0 69.0 69.0 69.0 69.0 69.0 69.0 69.0 69.0 69.1 69.1 69.1 69.0 69.1 69.1 69.2 69.2 69.1 69.1 69.6 69.2 69.2 69.1 69.1 69.1 69.1 69.1 69.0 69.0 69.0 68.9 69.0 68.9 68.9 68.8 68.8 68.9 68.9 68.8 68.7 68.7 68.8 68.9 68.8 68.7 68.8 68.8 68.8 68.8 68.7 68.7 68.7 68.8 68.7 68.7 68.6 68.7 68.8 68.7 68.7 68.5 68.6 68.7 68.7 68.7 68.6 68.5 68.6 68.6 68.6 68.5 68.4 68.5 68.5 68.4 68.4 76.1 71.0 68.4 68.4 68.4 68.3 68.3 68.3 68.3 68.3 68.2 68.2 68.2 68.2 68.3 68.1 68.0 68.0 67.9 67.9 67.9 67.9 67.8 67.8 67.8 67.8 67.8 67.7 67.6 67.7 67.8 67.7 67.6 67.6 67.6 67.7 67.7 67.6 67.5 67.5 67.6 67.5 67.5 67.4 67.4 67.5 67.5 67.5 67.3 67.3 67.5 67.5 67.5 67.4 67.4 67.4 67.5 67.4 67.4 67.3 67.3 67.4 67.4 67.3 67.3 67.5 67.3 67.4 67.3 67.3 67.2 67.3 67.3 67.3 67.2 67.2 67.2 67.3 67.3 67.3 67.3 67.2 67.3 67.6 67.3 67.4 67.4 67.5 67.5 67.5 67.5 67.6 67.6 67.7 67.7 67.7 67.8 67.8 67.8 67.8 67.8 67.8 67.8 67.8 67.8 67.8 67.8 67.9 67.9 67.9 67.9 67.9 68.0 68.0 68.0 68.1 68.2 68.2 68.2 68.2 68.2 68.3 68.4 68.3 68.3 68.3 68.4 68.4 68.4 68.2 68.1 68.2 68.2 68.2 68.1 68.1 68.1 68.1 68.1 68.1 68.0 68.0 68.1 68.1 68.0 67.9 67.8 67.9 67.9 67.9 67.8 67.8 67.8 67.9 67.9 67.8 67.7 67.7 67.8 67.8 67.7 67.7 67.6 67.7 67.7 67.6 67.6 67.7 67.6 67.6 67.5 67.5 67.4 67.4 67.4 67.4 67.4 67.4 67.3 67.4 67.4 67.4 67.4 67.4 67.3 67.4 67.3 67.3 67.3 67.3 67.3 67.3 67.3 67.3 67.2 67.2 67.2 67.2 67.2 67.1 67.1 67.1 67.0 67.0 66.9 66.9 79.8 85.4 85.4 85.2 85.7 85.4 85.1 85.9 83.0 66.3 66.2 66.2 66.2 66.1 66.0 65.9 65.9 65.9 65.8 65.7 65.6 65.6 65.6 65.5 65.4 65.3 65.3 83.8 92.0 91.5 91.0 90.5 90.9 90.3 90.5 88.3 65.0 64.7 64.7 64.6 64.5 64.4 64.4 64.3 64.2 64.1 64.0 63.9 63.8 63.7 63.5 63.4 63.5 63.4 63.3 63.1 63.0 62.8 62.7 62.6 62.5 62.3 62.2 62.1 62.2 61.9 61.8 61.6 61.6 61.5 61.4 61.3 68.2 69.7 69.5 69.5 68.8 68.5 68.9 67.8 64.1 60.9 60.9 60.9 60.8 60.9 60.8 60.8 60.7 60.6 60.6 60.5 60.4 60.3 60.2 60.2 60.1 60.1 60.0 60.0 60.1 60.0 60.0 60.0 60.0 60.0 60.0 60.0 60.0 60.0 60.0 60.0 60.1 60.4 60.3 ]
+BAD-SPECTRA=[0.0 59.7 59.7 59.7 59.7 59.7 59.7 59.7 59.8 59.8 59.8 59.8 59.8 59.8 60.3 59.8 59.8 59.8 59.8 63.1 62.5 59.8 61.3 59.9 60.2 59.9 59.8 63.6 59.9 61.0 60.6 60.4 61.8 68.8 64.5 62.4 72.1 66.5 63.5 72.9 71.5 66.2 62.7 64.2 64.6 64.1 66.0 66.1 66.1 66.9 68.2 68.4 67.4 69.5 69.3 68.2 70.3 69.7 69.7 69.1 70.7 70.6 69.3 71.2 70.6 69.6 71.1 70.5 70.6 69.3 70.9 70.9 69.6 71.4 71.0 70.4 71.0 71.3 70.8 69.2 70.4 69.8 68.8 70.1 69.9 69.8 69.9 71.1 71.0 69.9 71.6 71.2 70.3 71.7 71.1 70.9 70.2 71.1 70.8 69.8 70.8 70.4 70.1 70.6 70.5 70.4 69.7 70.6 70.5 69.8 70.5 70.2 69.8 70.0 70.3 70.1 69.5 70.2 70.0 69.6 70.2 69.9 69.7 69.6 70.1 69.9 69.5 69.9 69.8 69.6 70.0 69.7 69.6 69.4 69.9 69.7 69.5 69.8 69.6 69.6 69.9 69.8 69.9 69.5 70.0 70.0 69.7 70.3 70.1 70.0 70.1 70.6 70.6 69.9 70.8 70.6 70.0 70.9 70.6 70.3 69.9 70.5 70.2 69.6 70.0 69.6 69.3 69.7 69.5 69.3 69.0 69.2 69.2 68.9 68.9 68.7 68.6 68.8 68.8 68.6 68.5 68.5 68.6 68.6 68.6 68.5 68.4 68.5 68.6 68.5 68.4 79.2 73.0 68.4 68.5 68.4 68.3 68.3 68.5 68.4 68.4 68.6 68.5 68.4 68.6 68.6 68.5 68.3 68.5 68.4 68.2 68.6 68.4 68.2 68.3 68.4 68.4 68.1 68.3 68.2 67.9 68.2 68.0 67.8 67.7 67.7 67.7 67.6 67.6 67.4 67.3 67.5 67.5 67.3 67.2 67.3 67.3 67.3 67.2 67.1 67.1 67.2 67.3 67.2 67.1 67.1 67.1 67.2 67.2 67.1 67.0 67.1 67.1 67.2 67.0 67.0 67.3 67.1 67.2 67.1 67.0 67.0 67.1 67.2 67.1 67.1 67.1 67.1 67.3 67.2 67.1 67.2 67.2 67.3 67.2 67.3 67.3 67.3 67.5 67.4 67.4 67.5 67.5 67.6 67.6 67.6 67.8 67.7 67.8 67.9 67.8 67.9 67.8 67.8 67.8 67.7 67.8 67.7 67.7 67.9 67.7 67.7 67.8 67.8 67.9 67.9 67.9 68.0 68.0 68.1 68.0 68.0 68.3 68.2 68.3 68.2 68.3 68.5 68.4 68.4 68.2 68.1 68.2 68.1 68.1 68.0 67.9 68.0 67.9 67.9 67.9 67.8 67.9 67.9 67.8 67.8 67.7 67.6 67.7 67.7 67.7 67.6 67.6 67.6 67.6 67.6 67.5 67.4 67.4 67.5 67.5 67.4 67.4 67.3 67.4 67.4 67.3 67.3 67.5 67.3 67.3 67.3 67.3 67.2 67.2 67.2 67.3 67.2 67.2 67.2 67.2 67.2 67.3 67.2 67.3 67.2 67.3 67.2 67.2 67.2 67.2 67.2 67.2 67.2 67.2 67.1 67.1 67.1 67.1 67.0 67.0 67.0 67.0 67.0 66.9 66.9 66.9 84.6 90.8 91.0 91.1 90.9 91.4 91.4 91.2 89.1 66.3 66.3 66.3 66.2 66.1 66.0 66.0 66.0 66.0 65.9 65.8 65.7 65.7 65.7 65.6 65.5 65.4 65.4 84.9 90.8 91.5 90.2 90.7 90.9 90.1 90.5 88.6 65.0 64.8 64.8 64.7 64.6 64.5 64.4 64.4 64.3 64.1 64.0 63.9 63.8 63.7 63.5 63.4 63.5 63.4 63.3 63.1 62.9 62.8 62.6 62.6 62.4 62.3 62.1 62.0 62.0 61.8 61.8 61.6 61.5 61.4 61.3 61.3 64.0 65.2 65.9 65.6 66.5 67.0 66.9 67.1 62.9 60.9 60.9 60.9 60.8 60.8 60.8 60.7 60.7 60.6 60.6 60.5 60.4 60.3 60.2 60.1 60.0 60.0 59.9 59.9 59.9 59.9 59.9 59.9 59.9 59.9 59.9 59.8 59.9 59.9 59.8 59.9 60.1 60.2 60.3 ]
+
+SPECTRA-INFO=4,5,641999,SN,100,200,1520208724.588988
+MEAN-SPECTRA=[0.0 59.8 59.8 59.8 59.8 59.8 59.8 59.8 59.8 59.8 59.8 59.8 59.8 59.8 59.9 59.8 59.7 59.7 60.3 60.9 60.3 59.8 60.7 59.7 60.0 59.7 59.7 61.1 59.9 60.6 62.7 73.3 60.6 63.7 61.4 62.3 70.5 64.5 62.3 72.8 68.4 64.5 61.7 61.9 62.3 62.4 64.5 62.8 63.0 63.4 63.6 63.8 63.9 64.0 64.3 64.5 64.6 64.7 64.8 64.9 65.2 65.3 65.4 65.4 65.4 65.5 65.7 65.7 65.7 65.8 66.0 66.2 66.3 66.3 66.4 66.4 66.7 66.8 66.8 66.8 66.8 67.1 67.2 67.2 67.2 67.2 67.4 67.5 67.6 67.6 67.7 67.8 68.0 68.1 68.1 68.0 68.2 68.3 68.4 68.4 68.3 68.3 68.6 68.6 68.6 68.6 68.6 68.7 68.8 69.0 69.2 68.8 68.8 68.9 69.0 69.0 69.0 69.0 69.0 69.1 69.2 69.1 69.1 69.1 69.2 69.2 69.2 69.1 69.2 69.2 69.2 69.2 69.2 69.1 69.6 69.2 69.2 69.2 69.1 69.1 69.1 69.1 69.0 68.9 68.9 68.9 69.0 69.0 68.9 68.8 68.9 68.9 69.0 68.9 68.8 68.8 68.9 68.9 68.9 68.8 68.8 68.8 68.9 68.9 68.7 68.7 68.7 68.8 68.7 68.6 68.6 68.6 68.8 68.7 68.6 68.5 68.5 68.7 68.7 68.6 68.5 68.5 68.6 68.5 68.6 68.4 68.4 68.5 68.5 68.4 68.4 76.0 71.1 68.5 68.5 68.4 68.3 68.3 68.3 68.3 68.3 72.2 68.2 68.2 68.2 68.3 68.1 68.1 68.0 67.9 68.0 67.9 67.9 67.8 67.8 67.8 67.8 67.7 67.7 67.7 67.7 67.7 67.7 67.6 67.6 67.5 67.6 67.6 67.5 67.4 67.5 67.5 67.5 67.4 67.3 67.3 67.4 67.5 67.3 67.3 67.2 67.5 67.4 67.4 67.3 67.2 67.3 67.3 67.4 67.3 67.2 67.2 67.3 67.4 67.3 67.3 67.5 67.3 67.4 67.3 67.3 67.2 67.3 67.4 67.4 67.9 67.3 67.4 67.5 67.4 67.4 67.4 67.4 67.4 67.5 67.3 67.5 67.4 67.5 67.5 67.5 67.5 67.5 67.5 67.6 67.6 67.6 67.7 67.7 67.7 67.7 67.7 67.7 68.1 67.8 67.7 67.8 67.8 67.9 67.9 67.9 67.9 67.9 68.0 68.0 68.1 68.1 68.2 68.2 68.2 68.1 68.1 68.2 68.3 68.3 68.2 68.2 68.3 68.3 68.3 68.1 68.1 68.1 68.2 68.2 68.1 68.1 68.1 68.2 68.2 68.1 68.0 68.0 68.1 68.1 68.0 67.9 67.8 67.9 67.9 67.9 67.8 67.8 67.8 67.8 67.8 67.8 67.7 67.7 67.7 67.8 67.7 67.7 67.7 67.7 67.8 91.2 125.0 67.8 67.6 67.6 67.6 67.5 67.4 67.4 67.4 67.4 67.4 67.4 67.4 67.4 67.5 67.5 67.4 67.4 67.4 67.5 67.4 67.4 67.4 67.4 67.4 67.4 67.4 67.3 67.3 67.3 67.3 67.2 67.1 67.1 67.1 67.1 67.0 67.0 66.9 66.9 80.0 85.6 85.5 85.5 85.7 85.6 85.3 85.8 83.1 66.3 66.3 66.3 66.2 66.2 66.0 66.0 66.0 65.9 65.8 65.8 65.7 65.6 65.6 65.9 65.4 65.3 65.3 83.5 91.8 91.4 91.0 90.4 90.8 90.1 90.2 88.2 65.0 64.7 64.7 64.6 64.5 64.5 64.4 64.4 64.3 64.1 64.0 63.9 63.8 63.7 63.5 63.6 63.5 63.4 63.2 63.1 62.9 62.8 62.7 62.6 62.4 62.3 62.1 62.1 62.1 61.9 61.8 61.6 61.5 61.4 61.4 61.3 68.3 69.9 69.7 69.7 69.0 68.6 68.8 67.9 64.1 60.9 60.8 60.8 60.7 60.7 60.7 60.7 60.5 60.5 60.4 60.4 60.3 60.2 60.2 60.1 60.0 60.0 60.0 59.9 60.0 60.0 60.0 59.9 59.9 59.9 59.9 59.9 59.9 59.8 59.8 59.8 59.8 59.9 60.0 ]
+BAD-SPECTRA=[0.0 59.6 59.6 59.6 59.6 59.6 59.6 59.6 59.6 59.6 59.6 59.6 59.6 59.6 60.1 59.6 59.5 59.5 60.5 63.3 61.7 59.5 61.0 59.6 60.0 59.6 59.5 63.4 59.6 60.9 60.4 60.0 61.6 68.9 64.1 62.3 72.0 66.4 63.4 73.0 71.4 67.0 62.7 64.2 64.7 64.0 67.6 66.1 66.2 66.8 68.3 68.4 67.3 69.5 69.3 68.2 70.3 69.7 69.8 69.1 70.8 70.7 69.3 71.3 70.7 69.7 71.1 70.7 70.6 69.3 71.0 70.9 69.6 71.4 71.0 70.3 70.9 71.3 70.8 69.2 70.4 69.8 68.8 70.2 70.0 69.9 70.0 71.2 71.1 70.0 71.7 71.3 70.4 71.7 71.1 70.9 70.1 71.0 70.7 69.7 70.7 70.2 69.9 70.5 70.4 70.3 69.6 70.6 70.5 69.9 70.7 70.2 69.9 70.0 70.4 70.2 69.6 70.3 70.1 69.7 70.4 70.0 69.9 69.8 70.2 70.0 69.6 70.0 69.8 69.6 70.0 69.7 69.6 69.4 69.9 69.6 69.5 69.7 69.5 69.5 69.8 69.7 69.8 69.4 70.0 70.0 69.6 70.3 70.1 70.0 70.1 70.6 70.7 70.0 70.9 70.6 70.1 71.0 70.7 70.4 70.0 70.6 70.2 69.6 70.1 69.7 69.4 69.7 69.5 69.3 68.9 69.2 69.1 68.8 68.9 68.6 68.6 68.7 68.6 68.5 68.4 68.4 68.5 68.5 68.5 68.4 68.4 68.4 68.6 68.5 68.4 75.4 70.5 68.4 68.6 68.5 68.4 68.4 68.6 68.5 68.4 70.5 68.5 68.5 68.7 68.6 68.6 68.3 68.6 68.5 68.3 68.6 68.4 68.2 68.3 68.4 68.4 68.1 68.3 68.1 67.9 68.1 68.0 67.7 67.6 67.7 67.6 67.5 67.5 67.3 67.3 67.4 67.4 67.2 67.1 67.1 67.2 67.3 67.1 67.0 67.0 67.2 67.2 67.1 67.0 67.0 67.1 67.2 67.2 67.1 67.1 67.1 67.2 67.2 67.1 67.1 67.4 67.2 67.3 67.1 67.1 67.1 67.2 67.3 67.1 67.4 67.2 67.2 67.3 67.3 67.2 67.2 67.2 67.3 67.3 67.3 67.3 67.3 67.5 67.4 67.4 67.4 67.4 67.5 67.4 67.5 67.6 67.5 67.7 67.6 67.6 67.6 67.6 67.8 67.7 67.7 67.8 67.7 67.8 67.9 67.8 67.8 67.9 67.9 68.0 67.9 68.0 68.0 68.0 68.2 68.0 67.9 68.2 68.1 68.2 68.0 68.0 68.2 68.1 68.2 67.9 67.9 68.0 68.0 68.0 67.9 67.9 67.9 68.0 68.0 67.9 67.8 67.9 67.9 67.9 67.9 67.7 67.7 67.7 67.7 67.7 67.6 67.5 67.6 67.6 67.6 67.5 67.4 67.4 67.5 67.5 67.4 67.5 67.4 67.5 67.5 94.6 128.3 67.6 67.4 67.4 67.3 67.3 67.2 67.2 67.2 67.2 67.2 67.2 67.2 67.2 67.2 67.3 67.2 67.2 67.2 67.3 67.3 67.3 67.2 67.2 67.2 67.2 67.2 67.2 67.2 67.1 67.1 67.1 67.1 67.0 67.0 67.0 67.0 67.0 66.9 66.9 85.0 90.9 91.1 91.3 91.2 91.7 91.7 91.4 89.3 66.4 66.3 66.3 66.3 66.2 66.0 66.0 66.0 66.0 65.9 65.9 65.7 65.7 65.7 66.3 65.6 65.4 65.4 84.9 90.9 91.4 90.5 90.6 91.1 90.1 90.6 88.4 65.0 64.8 64.8 64.7 64.6 64.5 64.4 64.4 64.3 64.1 64.0 63.9 63.8 63.7 63.6 64.2 63.5 63.4 63.3 63.1 62.9 62.8 62.7 62.6 62.4 62.3 62.1 62.1 62.0 61.9 61.7 61.6 61.5 61.4 61.4 61.3 64.0 65.2 65.8 65.6 66.4 66.9 66.8 67.0 62.8 60.8 60.8 60.8 60.7 60.7 60.6 60.6 60.5 60.5 60.4 60.3 60.2 60.2 60.1 60.0 59.9 59.9 59.8 59.8 59.8 59.8 59.8 59.8 59.7 59.7 59.7 59.7 59.7 59.7 59.7 59.7 59.7 59.8 59.9 ]
+
+# OBS-ID-INFO=obsid,start_time,stop_time,obsid_samples
+OBS-ID-INFO=641999,1520208240.000,1520211840.000,59
+    :param content: list of strings representing the content of the file
+    :return: True if the content refers to a RTSM and it is not empty
+    """
+    if content:
+        pattern = r'(^#.*)|(^\w*-\w*=)|(^\w*-\w*-\w*=)|(^$)'
+        number_of_lines = len(content.split('\n'))
+        pattern_matching_lines = re.findall(pattern, content, re.MULTILINE)
+        number_pattern_matching_lines = len(pattern_matching_lines)
+        logger.info('RTSM: number of matched lines %d/%d number of lines in files',
+                    number_pattern_matching_lines,
+                    number_of_lines)
+        return number_of_lines == number_pattern_matching_lines
+    else:
+        return False
+
+
+def is_station_test(content):
+    """
+    Check if the content of the file have the structure of a station test.
+    Example content
+20130522,NFO,---,STATION,NAME=CS011C
+20130522,NFO,---,RUNTIME,START=03:10:00,STOP=05:52:27
+20130522,NFO,---,CHECKS,S1,O1,SP1,NS1=180,S3,O3,SP3,NS3=180,M,O5,SN,SP5,NS5=300,EO,ESP,EN=30,TV,TM
+20130522,NFO,---,STATISTICS,BAD_LBL=1,BAD_LBH=2,BAD_HBA=7
+20130522,LBL,---,TESTSIGNAL,SUBBANDX=301,SIGNALX=82.0,SUBBANDY=301,SIGNALY=81.8
+20130522,LBL,019,RF_FAIL,X=77.0,Y=70.1
+20130522,LBL,019,LOW_NOISE,Xproc=100.000,Xval=66.6,Xdiff=0.0,Xref=67.7
+20130522,LBH,---,TESTSIGNAL,SUBBANDX=301,SIGNALX=81.8,SUBBANDY=301,SIGNALY=82.2
+20130522,LBH,013,RF_FAIL,Y=72.9
+20130522,LBH,013,LOW_NOISE,Yproc=100.000,Yval=66.0,Ydiff=0.1,Yref=67.8
+20130522,LBH,015,RF_FAIL,X=68.0
+20130522,LBH,015,LOW_NOISE,Xproc=100.000,Xval=67.8,Xdiff=0.0,Xref=68.0
+20130522,HBA,003,E_FAIL,HNX13=64.3 2.3,JX13=1.0
+20130522,HBA,009,E_FAIL,HNX8=62.7 0.9
+20130522,HBA,010,HIGH_NOISE,Xproc=1.333,Xval=73.3,Xdiff=3.9,Xref=70.8,Yproc=0.500,Yval=71.5,Ydiff=3.1,Yref=70.9
+20130522,HBA,010,JITTER,Xproc=92.278,Xdiff=3.9,Xref=0.1,Yproc=99.611,Ydiff=3.1,Yref=0.2
+20130522,HBA,010,E_FAIL,HNY3=63.7 2.5,JY3=1.0,HNY12=64.0 2.5,JY12=1.0
+20130522,HBA,015,HIGH_NOISE,Xproc=8.278,Xval=74.2,Xdiff=5.6,Xref=70.8
+20130522,HBA,015,JITTER,Xproc=98.500,Xdiff=5.6,Xref=0.1
+20130522,HBA,015,SUMMATOR_NOISE,Y=1
+20130522,HBA,015,E_FAIL,HNX12=66.4 5.5,JX12=1.0
+20130522,HBA,019,JITTER,Xproc=99.333,Xdiff=1.9,Xref=0.1
+20130522,HBA,026,RF_FAIL,X=106.4 357 121.0 107.8 357 121.2
+20130522,HBA,036,SUMMATOR_NOISE,X=1,Y=1
+20130522,HBA,036,E_FAIL,SPX9=1,SPY9=1,SPX10=1,SPY10=1,SPX13=1,SPY13=1,SPX14=1,SPY14=1
+    :param content: list of strings representing the content of the file
+    :return: True if the content refers to a RTSM and it is not empty
+    """
+    if content:
+        pattern = r'(^#.*)|(^\d*,[A-z]{3},[\d-]{3})'
+        number_of_lines = len(content.splitlines())
+        pattern_matching_lines = re.findall(pattern, content, re.MULTILINE)
+        number_pattern_matching_lines = len(pattern_matching_lines)
+        logger.debug('Station Test: number of matched lines %d/%d number of lines in files',
+                     number_pattern_matching_lines,
+                     number_of_lines)
+
+        return number_of_lines == number_pattern_matching_lines
+    else:
+        return False