From 7adf087d215c3bb65acd425f894c44e3fd0d74ab Mon Sep 17 00:00:00 2001
From: Roy de Goei <goei@astron.nl>
Date: Tue, 23 Mar 2021 07:44:18 +0100
Subject: [PATCH] TMSS-687: Add Create StationTimeLine to the model to store
 sunrise, sunset, day, night calculation for all stations over a period

---
 .../backend/src/tmss/tmssapp/conversions.py   |  9 ++++
 .../tmss/tmssapp/migrations/0001_initial.py   | 15 ++++++
 .../src/tmss/tmssapp/models/CMakeLists.txt    |  1 +
 .../src/tmss/tmssapp/models/__init__.py       |  3 +-
 .../src/tmss/tmssapp/models/calculations.py   | 30 +++++++++++
 SAS/TMSS/backend/src/tmss/tmssapp/populate.py | 50 ++++++++++++++++++-
 .../tmss/tmssapp/serializers/CMakeLists.txt   |  1 +
 .../src/tmss/tmssapp/serializers/__init__.py  |  3 +-
 .../tmss/tmssapp/serializers/calculations.py  | 15 ++++++
 .../src/tmss/tmssapp/viewsets/CMakeLists.txt  |  3 +-
 .../src/tmss/tmssapp/viewsets/__init__.py     |  3 +-
 .../src/tmss/tmssapp/viewsets/calculations.py | 13 +++++
 SAS/TMSS/backend/src/tmss/urls.py             |  6 +++
 13 files changed, 147 insertions(+), 5 deletions(-)
 create mode 100644 SAS/TMSS/backend/src/tmss/tmssapp/models/calculations.py
 create mode 100644 SAS/TMSS/backend/src/tmss/tmssapp/serializers/calculations.py
 create mode 100644 SAS/TMSS/backend/src/tmss/tmssapp/viewsets/calculations.py

diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/conversions.py b/SAS/TMSS/backend/src/tmss/tmssapp/conversions.py
index ae926e172f4..819e3e4983a 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/conversions.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/conversions.py
@@ -5,6 +5,8 @@ from astropy.coordinates.earth import EarthLocation
 from astropy.coordinates import Angle, get_body
 import astropy.time
 from functools import lru_cache
+from lofar.sas.tmss.tmss.tmssapp.models.calculations import StationTimeline
+
 
 import logging
 logger = logging.getLogger(__name__)
@@ -54,6 +56,13 @@ def timestamps_and_stations_to_sun_rise_and_set(timestamps: tuple, stations: tup
     return_dict = {}
     for station in stations:
         for timestamp in timestamps:
+            # TODO continue on this part
+            print("find %s %s" % (station, datetime.date(timestamp)))
+            if StationTimeline.objects.filter(station_name=station, timestamp=datetime.date(timestamp)).count() >0:
+                logger.info("Station %s %s gevonden" % (station,timestamp))
+                # so getit from database and fill in
+                # else calculate and add to database
+            #print("hey")
             # todo: this can probably be made faster by moving the following logic to an own function with single station/timestamp as input and putting the lru_cache on there.
             #  This also means that we have to strip the time from the datetime. Can this be safely done?
             observer = create_astroplan_observer_for_station(station)
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py b/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py
index 2df67c9b37f..8a62e2862a4 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py
@@ -895,6 +895,21 @@ class Migration(migrations.Migration):
                 ('second', models.ForeignKey(help_text='Second Task Blueprint to connect.', on_delete=django.db.models.deletion.CASCADE, related_name='second_scheduling_relation', to='tmssapp.TaskBlueprint')),
             ],
         ),
+        migrations.CreateModel(
+            name='StationTimeline',
+            fields=[
+                ('station_name', models.CharField(max_length=16, null=False, editable=False, help_text='The LOFAR station name.')),
+                ('timestamp', models.DateField(editable=False, null=True, help_text='The date (YYYYMMDD).')),
+                ('sunrise_start', models.DateTimeField(null=True, help_text='Start time of the sunrise.')),
+                ('sunrise_end', models.DateTimeField(null=True, help_text='End time of the sunrise.')),
+                ('sunset_start', models.DateTimeField(null=True, help_text='Start time of the sunset.')),
+                ('sunset_end', models.DateTimeField(null=True, help_text='End time of the sunset.')),
+                ('day_start', models.DateTimeField(null=True, help_text='Start time of the day.')),
+                ('day_end', models.DateTimeField(null=True, help_text='End time of the day.')),
+                ('night_start', models.DateTimeField(null=True, help_text='Start time of the night.')),
+                ('night_end', models.DateTimeField(null=True, help_text='End time of the night.'))
+            ],
+        ),
         migrations.AddConstraint(
             model_name='taskrelationselectiontemplate',
             constraint=models.UniqueConstraint(fields=('name', 'version'), name='taskrelationselectiontemplate_unique_name_version'),
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/models/CMakeLists.txt b/SAS/TMSS/backend/src/tmss/tmssapp/models/CMakeLists.txt
index 3496efd5735..f6e74f93da0 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/models/CMakeLists.txt
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/models/CMakeLists.txt
@@ -8,6 +8,7 @@ set(_py_files
     scheduling.py
     common.py
     permissions.py
+    calculations.py
     )
 
 python_install(${_py_files}
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/models/__init__.py b/SAS/TMSS/backend/src/tmss/tmssapp/models/__init__.py
index 0b0546b8d4b..3eb788371d9 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/models/__init__.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/models/__init__.py
@@ -1,4 +1,5 @@
 from .specification import *
 from .scheduling import *
 from .common import *
-from .permissions import *
\ No newline at end of file
+from .permissions import *
+from .calculations import *
\ No newline at end of file
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/models/calculations.py b/SAS/TMSS/backend/src/tmss/tmssapp/models/calculations.py
new file mode 100644
index 00000000000..b1b233acb4f
--- /dev/null
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/models/calculations.py
@@ -0,0 +1,30 @@
+"""
+This file contains the database models for calculations
+"""
+
+import os
+import logging
+logger = logging.getLogger(__name__)
+
+from django.db.models import Model, CharField, DateTimeField, DateField
+
+
+class StationTimeline(Model):
+    """
+    Represents computations of sunrise, sunset, day and night of the given stations at the given timestamps.
+    The day/sunrise/sunset is always on the date of the timestamp.
+    The night is usually the one _starting_ on the date of the time stamp, unless the given timestamp falls
+    before sunrise, in which case it is the night _ending_ on the timestamp date.
+    """
+    station_name = CharField(max_length=16, null=False, editable=False, help_text='The LOFAR station name.')
+    timestamp = DateField(editable=False, null=True, help_text='The date (YYYYMMDD).')
+
+    sunrise_start = DateTimeField(null=True, help_text='Start time of the sunrise.')
+    sunrise_end = DateTimeField(null=True, help_text='End time of the sunrise.')
+    sunset_start = DateTimeField(null=True, help_text='Start time of the sunset.')
+    sunset_end = DateTimeField(null=True, help_text='End time of the sunset.')
+    day_start = DateTimeField(null=True, help_text='Start time of the day.')
+    day_end = DateTimeField(null=True, help_text='End time of the day.')
+    night_start = DateTimeField(null=True, help_text='Start time of the night.')
+    night_end = DateTimeField(null=True, help_text='End time of the night.')
+
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/populate.py b/SAS/TMSS/backend/src/tmss/tmssapp/populate.py
index d9352294552..aa30967cd2b 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/populate.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/populate.py
@@ -19,12 +19,13 @@ logger = logging.getLogger(__name__)
 
 import inspect
 import re
-from datetime import datetime, timezone
+from datetime import timezone, datetime, date
 from lofar.sas.tmss.tmss.tmssapp import models
 from lofar.sas.tmss.tmss.tmssapp import viewsets
 from lofar.sas.tmss.tmss.tmssapp.models.specification import *
 from lofar.sas.tmss.tmss.tmssapp.models.scheduling import *
 from lofar.sas.tmss.tmss.tmssapp.models.permissions import *
+from lofar.sas.tmss.tmss.tmssapp.conversions import timestamps_and_stations_to_sun_rise_and_set
 from lofar.common import isTestEnvironment, isDevelopmentEnvironment
 from concurrent.futures import ThreadPoolExecutor
 from django.contrib.auth.models import User, Group, Permission
@@ -67,6 +68,9 @@ def populate_test_data():
             from lofar.sas.tmss.tmss.tmssapp.subtasks import schedule_subtask
             from lofar.common.json_utils import get_default_json_object_for_schema
 
+            # Maybe move to 'migrations populate'
+            populate_calculations()
+
             constraints_template = models.SchedulingConstraintsTemplate.objects.get(name="constraints")
             constraints_spec = get_default_json_object_for_schema(constraints_template.schema)
 
@@ -479,3 +483,47 @@ def populate_system_test_users():
     guest_user.groups.add(Group.objects.get(name='Guest'))
     lta_user = User.objects.create(username='lta_user', password='lta_user')
     lta_user.groups.add(Group.objects.get(name='LTA User'))
+
+
+def populate_calculations():
+    """
+    Calculate a week of station timeline data of all stations
+    will take about a minute
+    TODO create a service which will do this continiously ?
+    """
+    starttime = datetime.utcnow()
+    logger.info("Populate sunrise, sunset, day, night for ALL stations from today until next week")
+    lst_timestamps = []
+    nbr_days = 7
+    for i in range(0, nbr_days):
+        dt = datetime.combine(date.today(), datetime.min.time()) + timedelta(days=i)
+        lst_timestamps.append(dt)
+
+    # Get all stations --> how to retrieve them?
+    lst_stations = ["CS001", "CS002", "CS003", "CS004", "CS005", "CS006", "CS007", "CS011", "CS013", "CS017", "CS021",
+                    "CS024",
+                    "CS026", "CS028", "CS030", "CS031", "CS032", "CS101", "CS103", "CS201", "CS301", "CS302", "CS401",
+                    "CS501"]
+                    # "RS104", "RS106", "RS205", "RS208", "RS210", "RS305", "RS306", "RS307", "RS310", "RS406", "RS407",
+                    # "RS409",
+                    # "RS410", "RS503", "RS508", "RS509",
+                    # "DE601", "DE602", "DE603", "DE604", "DE605", "FR606", "SE607", "UK608", "DE609", "PL610", "PL611",
+                    # "PL612",
+                    # "IE613", "LV614"]
+
+    json_result = timestamps_and_stations_to_sun_rise_and_set(tuple(lst_timestamps), tuple(lst_stations))
+    for station in lst_stations:
+        for i in range(len(lst_timestamps)):
+            station_timeline = models.StationTimeline.objects.create(
+                                                    station_name=station,
+                                                    timestamp=lst_timestamps[i],
+                                                    sunrise_start=json_result[station]['sunrise'][i]['start'],
+                                                    sunrise_end=json_result[station]['sunrise'][i]['end'],
+                                                    sunset_start=json_result[station]['sunset'][i]['start'],
+                                                    sunset_end=json_result[station]['sunset'][i]['end'],
+                                                    day_start=json_result[station]['day'][i]['start'],
+                                                    day_end=json_result[station]['day'][i]['end'],
+                                                    night_start= json_result[station]['night'][i]['start'],
+                                                    night_end=json_result[station]['night'][i]['end'])
+            logger.debug("station_timeline %s created for station %s " % (station_timeline, station))
+    logger.info("Done in %.1fs", (datetime.utcnow()-starttime).total_seconds())
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/serializers/CMakeLists.txt b/SAS/TMSS/backend/src/tmss/tmssapp/serializers/CMakeLists.txt
index 83a8174527b..f5f6fe38336 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/serializers/CMakeLists.txt
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/serializers/CMakeLists.txt
@@ -8,6 +8,7 @@ set(_py_files
     widgets.py
     common.py
     permissions.py
+    calculations.py
     )
 
 python_install(${_py_files}
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/serializers/__init__.py b/SAS/TMSS/backend/src/tmss/tmssapp/serializers/__init__.py
index 0b0546b8d4b..3eb788371d9 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/serializers/__init__.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/serializers/__init__.py
@@ -1,4 +1,5 @@
 from .specification import *
 from .scheduling import *
 from .common import *
-from .permissions import *
\ No newline at end of file
+from .permissions import *
+from .calculations import *
\ No newline at end of file
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/serializers/calculations.py b/SAS/TMSS/backend/src/tmss/tmssapp/serializers/calculations.py
new file mode 100644
index 00000000000..8584228204e
--- /dev/null
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/serializers/calculations.py
@@ -0,0 +1,15 @@
+"""
+This file contains the serializers for conversion models
+"""
+
+import logging
+logger = logging.getLogger(__name__)
+
+from rest_framework import serializers
+from .. import models
+
+
+class StationTimelineSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = models.StationTimeline
+        fields = '__all__'
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/CMakeLists.txt b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/CMakeLists.txt
index 186d29924f2..ab71ce95fb8 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/CMakeLists.txt
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/CMakeLists.txt
@@ -8,7 +8,8 @@ set(_py_files
     scheduling.py
     permissions.py
     project_permissions.py
-    )
+    calculations.py
+   )
 
 python_install(${_py_files}
     DESTINATION lofar/sas/tmss/tmss/tmssapp/viewsets)
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/__init__.py b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/__init__.py
index 0f7980fabfd..6f585af0a1c 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/__init__.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/__init__.py
@@ -1,4 +1,5 @@
 from .specification import *
 from .scheduling import *
 from .permissions import *
-from .project_permissions import *
\ No newline at end of file
+from .project_permissions import *
+from .calculations import *
\ No newline at end of file
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/calculations.py b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/calculations.py
new file mode 100644
index 00000000000..fd7eb3fbfea
--- /dev/null
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/calculations.py
@@ -0,0 +1,13 @@
+from .. import models
+from .. import serializers
+from .lofar_viewset import LOFARViewSet
+
+
+#
+# Conversions ViewSets
+#
+
+class StationTimelineViewSet(LOFARViewSet):
+    queryset = models.StationTimeline.objects.all()
+    serializer_class = serializers.StationTimelineSerializer
+
diff --git a/SAS/TMSS/backend/src/tmss/urls.py b/SAS/TMSS/backend/src/tmss/urls.py
index 039b531a658..59624904853 100644
--- a/SAS/TMSS/backend/src/tmss/urls.py
+++ b/SAS/TMSS/backend/src/tmss/urls.py
@@ -224,6 +224,12 @@ router.register(r'sip_identifier', viewsets.SIPidentifierViewSet)
 router.register(r'project_role', viewsets.ProjectRoleViewSet)
 router.register(r'project_permission', viewsets.ProjectPermissionViewSet)
 
+
+# CONVERSIONS
+
+router.register(r'station_timeline', viewsets.StationTimelineViewSet)
+
+
 urlpatterns.extend(router.urls)
 
 frontend_urlpatterns = [
-- 
GitLab