Commit 9a4dbcf2c2ec2afb2547b717e24e1a753a75f3c6

Authored by Alexis Koralewski
2 parents ea656830 ea467949
Exists in dev

Merge branch 'dev' of https://gitlab.irap.omp.eu/pyros-irap/pyros into dev

.gitignore
... ... @@ -77,8 +77,8 @@ PYROS_OBSERVATORY/*
77 77 /config/pyros_observatory/pyros_observatory_default/history/
78 78 #config/pyros_observatory/*/obsconfig.p
79 79 /config/pyros_observatory/pyros_observatory_default/obsconfig.p
80   -/src/core/pyros_django/obs_config/fixtures/obsconfig.p
81   -/src/core/pyros_django/obs_config/fixtures/history/*
  80 +src/core/pyros_django/obs_config/fixtures/obsconfig.p
  81 +src/core/pyros_django/obs_config/fixtures/history/*
82 82  
83 83 /src/core/pyros_django/sequences_pickle/
84 84 #src/core/guitastro
... ... @@ -110,3 +110,5 @@ ___*
110 110 client.log
111 111  
112 112 #privatedev/plugin/agent/triton/triton/CATALOGUES
  113 +
  114 +*de421.bsp
... ...
config/pyros_observatory/general/devices/device_FLI_KITS_generic.yml
... ... @@ -5,17 +5,17 @@ schema: schema_device-2.0.yml
5 5 DEVICE:
6 6  
7 7 inventory_label: Undefined
8   - manufacturer: FLI
9   - model: Kepler Image Time Stamp
10   - description: "GPS FLI Kepler" # opt str
  8 + manufacturer: GARMIN
  9 + model: Kepler Image Time Stamp (FITS)
  10 + description: "GPS 19x HVSM/N M1A1GN00" # opt str
11 11 sn: Undefined
12 12 power:
13   - voltage: 12
14   - intensity: 1
  13 + voltage: 7.33
  14 + intensity: 0.1
15 15 socket: "MiniDIN 8 pins"
16 16  
17 17 connector:
18   - input: "GPS radio waves"
  18 + input: "GLONASS radio waves"
19 19 output: "Meta data"
20 20  
21 21 comm:
... ...
config/pyros_observatory/general/schemas/schema_device-2.0.yml
... ... @@ -123,9 +123,9 @@ schema;schema_device:
123 123 required: False
124 124 mapping:
125 125 voltage:
126   - type: int
  126 + type: float
127 127 intensity:
128   - type: number
  128 + type: float
129 129 socket:
130 130 type: str
131 131 hostname:
... ...
config/pyros_observatory/general/schemas/schema_observatory-2.0.yml
... ... @@ -317,6 +317,20 @@ schema;schema_FN_CONTEXTS:
317 317 type: str
318 318 pathnaming:
319 319 type: str
  320 + pyros_seq_tmp:
  321 + type: map
  322 + required: False
  323 + mapping:
  324 + root_dir:
  325 + type: str
  326 + description:
  327 + type: str
  328 + extension:
  329 + type: str
  330 + naming:
  331 + type: str
  332 + pathnaming:
  333 + type: str
320 334 pyros_eph:
321 335 type: map
322 336 required: False
... ... @@ -525,6 +539,9 @@ schema;schema_UNIT:
525 539 home:
526 540 type: str
527 541 required: True
  542 + duskelev:
  543 + type: str
  544 + required: True
528 545 horizon:
529 546 type: map
530 547 required: True
... ...
docker/docker-compose.yml
... ... @@ -133,7 +133,7 @@ services:
133 133 resources:
134 134 limits:
135 135 cpus: '1'
136   - memory: 8GB
  136 + memory: 32GB
137 137 #pids: 1
138 138 # create network to allow images to communicate with other images within the same network
139 139 # networks:
... ...
src/core/pyros_django/dashboard/templates/dashboard/observation_index.html
... ... @@ -102,6 +102,24 @@
102 102 </a>
103 103 </div>
104 104 </li>
  105 + <li>
  106 + {% comment %}
  107 + Old version to be remastered :
  108 + <a href="{% url "proposal" %}">
  109 + <div class="all-info">
  110 +
  111 + <h3>Proposal</h3>
  112 + {% load static %} <img src="{% static "media/proposal.png" %}" alt="html5" height="180" width="180" />
  113 + </div></a>
  114 + {% endcomment %}
  115 + <div class="all-info">
  116 + <a href="{% url "quota_sp" %}">
  117 +
  118 + <h3>Quota Scientific programs</h3>
  119 + {% load static %} <img src="{% static "media/proposal.png" %}" alt="html5" />
  120 + </a>
  121 + </div>
  122 + </li>
105 123 {% endif %}
106 124 {# TBD #}
107 125 {% if USER_LEVEL|ifinlist:"Admin,Operator,Unit-PI,Management board member,Observer,TAC" %}
... ...
src/core/pyros_django/majordome/agent/Agent.py
... ... @@ -154,7 +154,8 @@ import config.old_config as config_old
154 154 #from config import *
155 155  
156 156 from majordome.models import AgentSurvey, AgentCmd, AgentLogs
157   -from user_mgmt.models import Period, Quota
  157 +from user_mgmt.models import Period
  158 +from scp_mgmt.models import Quota
158 159  
159 160 from vendor.guitastro.src.guitastro import Ephemeris
160 161 import pickle
... ... @@ -3549,23 +3550,23 @@ class Agent:
3549 3550 self._oc['config'].fn.fcontext = "pyros_eph"
3550 3551  
3551 3552 # --- Check if durations.pickle file exists
3552   - rootdir = self._oc['config'].fn.rootdir
3553   - filename = os.path.join(rootdir, period_id, "durations.pickle")
3554   - self.dprint(f"Read {filename=}")
3555   - if os.path.exists(filename):
3556   - self._oc['config'].fn.fcontext = fcontext0
3557   - return pickle.load(open(filename, "wb"))
  3553 + # rootdir = self._oc['config'].fn.rootdir
  3554 + # filename = os.path.join(rootdir, period_id, "durations.pickle")
  3555 + # log.info(f"Read {filename=}")
  3556 + # if os.path.exists(filename):
  3557 + # self._oc['config'].fn.fcontext = fcontext0
  3558 + # return pickle.load(open(filename, "wb"))
3558 3559  
3559 3560 # --- Get the period limit dates jd1, jd2
3560   - self.dprint(f"{dir(operiod)=}")
  3561 + log.info(f"{dir(operiod)=}")
3561 3562 d = operiod.start_date.isoformat()
3562 3563 night_start = d[0:4]+d[5:7]+d[8:10]
3563 3564 jd1, _ = self.config.fn.night2date(night_start)
3564 3565 d = operiod.end_date.isoformat()
3565 3566 night_end = d[0:4]+d[5:7]+d[8:10]
3566 3567 jd2, _ = self.config.fn.night2date(night_end)
3567   - self.dprint(f"{night_start=} {night_end=}")
3568   - self.dprint(f"{jd1=} {jd2=}")
  3568 + log.info(f"{night_start=} {night_end=}")
  3569 + log.info(f"{jd1=} {jd2=}")
3569 3570  
3570 3571 # --- Loop over dates of the period to create ephems
3571 3572 targets = ['sun', 'moon']
... ... @@ -3576,10 +3577,10 @@ class Agent:
3576 3577 d_cur = 0
3577 3578 d_total = 0
3578 3579 night_info = {}
  3580 + self._duskelev = self.config.getDuskElev()
3579 3581 while jd < jd2:
3580 3582 night = self._oc['config'].fn.date2night(jd)
3581 3583 for target in targets:
3582   - self.dprint(f"{night=} {target=}")
3583 3584 ephem = self.ephem_target2night(target, jd)
3584 3585 if target == "sun":
3585 3586 ks = np.where(ephem['alt'] < self._duskelev)
... ... @@ -3589,20 +3590,27 @@ class Agent:
3589 3590 jd += 1
3590 3591 d_total = d_prev + d_cur
3591 3592 for key, val in night_info.items():
  3593 + night_id = key
3592 3594 val[0] = d_total
3593 3595 val[-1] = d_total - val[-2]
3594 3596 # --- update db TODO
  3597 + try:
  3598 + if Quota.objects.get(night_id=night_id):
  3599 + # If quota for that night already exists in db, delete it
  3600 + Quota.objects.get(night_id=night_id).delete()
  3601 + except:
  3602 + Quota.objects.filter(night_id=night_id).delete()
3595 3603 quota = Quota()
3596 3604 quota_attributes = {}
3597 3605 quota_attributes["id_period"] = operiod.id
3598   - quota_attributes["night_id"] = night
3599   - quota_attributes["d_previousq"] = d_prev
3600   - quota_attributes["d_currentq"] = d_cur
3601   - quota_attributes["d_totalq"] = d_total
3602   - quota_attributes["d_nextq"] = d_total - d_cur
  3606 + quota_attributes["night_id"] = night_id
  3607 + quota_attributes["d_total"] = val[0]
  3608 + quota_attributes["d_previous"] = val[1]
  3609 + quota_attributes["d_current"] = val[2]
  3610 + quota_attributes["d_next"] = val[-1]
3603 3611 quota.set_attributes_and_save(quota_attributes)
3604   - operiod.quota = quota
3605   - operiod.save()
  3612 + operiod.quota = Quota.objects.get(night_id=list(night_info.keys())[0], id_period=operiod.id)
  3613 + operiod.save()
3606 3614 #log.info(f"Write {filename=}")
3607 3615 #pickle.dump(night_info, open(filename, "wb"))
3608 3616 return night_info
... ...
src/core/pyros_django/misc/fixtures/initial_fixture_dev_TZ.json
... ... @@ -211,6 +211,49 @@
211 211 "description_long": "",
212 212 "sp_pi": 2,
213 213 "science_theme": 1,
  214 + "quota_f":0.9,
  215 + "is_auto_validated": true
  216 + }
  217 + },
  218 + {
  219 + "model": "user_mgmt.scientificprogram",
  220 + "pk": 3,
  221 + "fields": {
  222 + "name": "debris-test",
  223 + "institute": 1,
  224 + "description_short": "",
  225 + "description_long": "",
  226 + "sp_pi": 2,
  227 + "science_theme": 1,
  228 + "quota_f":0.1,
  229 + "is_auto_validated": true
  230 + }
  231 + },
  232 + {
  233 + "model": "user_mgmt.scientificprogram",
  234 + "pk": 2,
  235 + "fields": {
  236 + "name": "irap",
  237 + "institute": 2,
  238 + "description_short": "",
  239 + "description_long": "",
  240 + "sp_pi": 5,
  241 + "science_theme": 1,
  242 + "quota_f": "0.7",
  243 + "is_auto_validated": true
  244 + }
  245 + },
  246 + {
  247 + "model": "user_mgmt.scientificprogram",
  248 + "pk": 4,
  249 + "fields": {
  250 + "name": "irap-test",
  251 + "institute": 2,
  252 + "description_short": "",
  253 + "description_long": "",
  254 + "sp_pi": 5,
  255 + "science_theme": 1,
  256 + "quota_f": "0.3",
214 257 "is_auto_validated": true
215 258 }
216 259 },
... ... @@ -226,6 +269,39 @@
226 269 }
227 270 },
228 271 {
  272 + "model": "user_mgmt.SP_Period",
  273 + "pk": 3,
  274 + "fields": {
  275 + "period": 1,
  276 + "scientific_program": 3,
  277 + "status": "Accepted",
  278 + "public_visibility": "Name",
  279 + "is_valid": "Accepted"
  280 + }
  281 + },
  282 + {
  283 + "model": "user_mgmt.SP_Period",
  284 + "pk": 4,
  285 + "fields": {
  286 + "period": 1,
  287 + "scientific_program": 4,
  288 + "status": "Accepted",
  289 + "public_visibility": "Name",
  290 + "is_valid": "Accepted"
  291 + }
  292 + },
  293 + {
  294 + "model": "user_mgmt.SP_Period",
  295 + "pk": 2,
  296 + "fields": {
  297 + "period": 1,
  298 + "scientific_program": 2,
  299 + "status": "Accepted",
  300 + "public_visibility": "Name",
  301 + "is_valid": "Accepted"
  302 + }
  303 + },
  304 + {
229 305 "model": "user_mgmt.SP_Period_User",
230 306 "pk": 2,
231 307 "fields": {
... ... @@ -250,6 +326,78 @@
250 326 }
251 327 },
252 328 {
  329 + "model": "user_mgmt.SP_Period_User",
  330 + "pk": 5,
  331 + "fields": {
  332 + "SP_Period": 2,
  333 + "user": 5
  334 + }
  335 + },
  336 + {
  337 + "model": "user_mgmt.SP_Period_User",
  338 + "pk": 6,
  339 + "fields": {
  340 + "SP_Period": 2,
  341 + "user": 8
  342 + }
  343 + },
  344 + {
  345 + "model": "user_mgmt.SP_Period_User",
  346 + "pk": 6,
  347 + "fields": {
  348 + "SP_Period": 2,
  349 + "user": 7
  350 + }
  351 + },
  352 + {
  353 + "model": "user_mgmt.SP_Period_User",
  354 + "pk": 7,
  355 + "fields": {
  356 + "SP_Period": 4,
  357 + "user": 5
  358 + }
  359 + },
  360 + {
  361 + "model": "user_mgmt.SP_Period_User",
  362 + "pk": 8,
  363 + "fields": {
  364 + "SP_Period": 4,
  365 + "user": 8
  366 + }
  367 + },
  368 + {
  369 + "model": "user_mgmt.SP_Period_User",
  370 + "pk": 9,
  371 + "fields": {
  372 + "SP_Period": 4,
  373 + "user": 7
  374 + }
  375 + },
  376 + {
  377 + "model": "user_mgmt.SP_Period_User",
  378 + "pk": 10,
  379 + "fields": {
  380 + "SP_Period": 3,
  381 + "user": 3
  382 + }
  383 + },
  384 + {
  385 + "model": "user_mgmt.SP_Period_User",
  386 + "pk": 11,
  387 + "fields": {
  388 + "SP_Period": 3,
  389 + "user": 4
  390 + }
  391 + },
  392 + {
  393 + "model": "user_mgmt.SP_Period_User",
  394 + "pk": 12,
  395 + "fields": {
  396 + "SP_Period": 3,
  397 + "user": 17
  398 + }
  399 + },
  400 + {
253 401 "model": "devices.telescope",
254 402 "pk": 1,
255 403 "fields": {
... ...
src/core/pyros_django/obs_config/obsconfig_class.py
... ... @@ -1239,6 +1239,16 @@ class OBSConfig:
1239 1239 """
1240 1240 home = self.get_unit_by_name(self.unit_name).get("home")
1241 1241 return home
  1242 +
  1243 + def getDuskElev(self)->float:
  1244 + """
  1245 + Return duskelev of current unit
  1246 +
  1247 + Returns:
  1248 + float: string reprensenting duskelev of unit
  1249 + """
  1250 + duskelev = float(self.get_unit_by_name(self.unit_name).get("duskelev"))
  1251 + return duskelev
1242 1252  
1243 1253 def get_agent_path_data_root(self, agent_name:str, computer_hostname=None) -> str:
1244 1254 """
... ...
src/core/pyros_django/scheduling/A_Scheduler.py
... ... @@ -5,6 +5,9 @@
5 5 # Linux console:
6 6 # cd /srv/develop/pyros/docker
7 7 # ./PYROS_DOCKER_START.sh
  8 +# ./PYROS_RUN_WEBSERVER_ONLY -o tnc -fg
  9 +# cd ..
  10 +# ./PYROS start -o tnc -fg -a A_Scheduler
8 11 #
9 12 # Launch from Power Shell:
10 13 # To go from docker to Powershell: pyros_user@ORION:~/app$ exit (or Ctrl+d)
... ... @@ -29,6 +32,7 @@ import argparse
29 32 import os
30 33 import pickle
31 34 import socket
  35 +import yaml
32 36  
33 37 pwd = os.environ['PROJECT_ROOT_PATH']
34 38 if pwd not in sys.path:
... ... @@ -44,6 +48,7 @@ for short_path in short_paths:
44 48 from majordome.agent.Agent import Agent, build_agent, log, parse_args
45 49 from seq_submit.models import Sequence
46 50 from user_mgmt.models import Period, ScientificProgram, SP_Period
  51 +from scp_mgmt.models import Quota
47 52 from scheduling.models import PredictiveSchedule, EffectiveSchedule
48 53 # = Specials
49 54 import glob
... ... @@ -54,6 +59,8 @@ from decimal import Decimal
54 59 import zoneinfo
55 60 import numpy as np
56 61  
  62 +from pyros_api import PyrosAPI
  63 +
57 64 class A_Scheduler(Agent):
58 65  
59 66 DPRINT = True
... ... @@ -223,10 +230,12 @@ class A_Scheduler(Agent):
223 230 """
224 231  
225 232  
226   - def update_db_quota_sequence(sequence, quota_attributes, id_period, night_id, d_total=sequence_info['duration']):
227   - sequence_quota = sequence.quota
228   - sp_quota = sequence.scientific_program
229   - institute_quota =
  233 + def update_db_quota_sequence(self, sequence_id, quota_attributes):
  234 + sequence = Sequence.objects.get(id=sequence_id)
  235 + new_quota = Quota()
  236 + new_quota.set_attributes_and_save(quota_attributes)
  237 + sequence.quota = new_quota
  238 + sequence.save()
230 239  
231 240 def _compute_schedule_1(self):
232 241 """Simple scheduler based on selection-insertion one state algorithm.
... ... @@ -236,6 +245,10 @@ class A_Scheduler(Agent):
236 245 """
237 246 t0 = time.time()
238 247 self.DPRINT = True
  248 +
  249 + # ===================
  250 + # --- Initializations
  251 + # ===================
239 252 # --- Get the incoming directory of the night
240 253 info = self.get_infos()
241 254 rootdir = info['rootdir']
... ... @@ -243,13 +256,14 @@ class A_Scheduler(Agent):
243 256 # --- Get the night
244 257 night = info['night']
245 258 # --- Get ephemeris informations of the night and initialize quotas
246   - night_info = self.update_sun_moon_ephems()
247   - quota_total_period = night_info['total'][1]
248   - quota_total_night_start = night_info[night][0]
249   - quota_total_night_end = night_info[night][1]
250   - self.dprint(f"{quota_total_period=}")
251   - self.dprint(f"{quota_total_night_start=}")
252   - self.dprint(f"{quota_total_night_end=}")
  259 + self.update_sun_moon_ephems()
  260 +
  261 + # Get quota for night
  262 +
  263 + oquota = Quota.objects.get(id_period=info["operiod"].id, night_id=info["night"])
  264 +
  265 +
  266 +
253 267 # --- Build the wildcard to list the sequences
254 268 wildcard = os.path.join(rootdir, subdir, "*.p")
255 269 self.dprint(f"{wildcard=}")
... ... @@ -263,7 +277,10 @@ class A_Scheduler(Agent):
263 277 schedule_order = np.zeros(self.BINS_NIGHT, dtype=int) -1
264 278 schedule_jd = np.zeros(self.BINS_NIGHT, dtype=float)
265 279 schedule_scientific_programm_id = np.zeros(self.BINS_NIGHT, dtype=int) -1
  280 +
  281 + # ===========================================================================================================
266 282 # --- Initialize the predictive schedule by the effective schedule from start of the current instant (=index)
  283 + # ===========================================================================================================
267 284 try:
268 285 last_schedule = EffectiveSchedule.objects.last()
269 286 except EffectiveSchedule.DoesNotExist:
... ... @@ -285,7 +302,7 @@ class A_Scheduler(Agent):
285 302 schedule_jd[0:index] = schedule_eff_jd[0:index]
286 303 schedule_scientific_programm_id[0:index] = schedule_eff_scientific_programm_id[0:index]
287 304 else:
288   - # --- Case when there is not ever effective schedule for this night
  305 + # --- Case when there is no effective schedule for this night
289 306 print(f"No effective schedule for this night {night}")
290 307 else:
291 308 # --- Case of invalid entry in the database
... ... @@ -314,12 +331,13 @@ class A_Scheduler(Agent):
314 331 if os.path.exists(ephfile):
315 332 self.dprint(f"Read file {seqfile}")
316 333 # --- seq_info = sequence dictionary
317   - # --- eph_info = ephemeris dictionary
318 334 seq_info = pickle.load(open(seqfile,"rb"))
319 335 #print("="*20 + "\n" + f"{seq_info=}")
  336 + # --- eph_info = ephemeris dictionary
320 337 eph_info = pickle.load(open(ephfile,"rb"))
321 338 #print("="*20 + "\n" + f"{eph_info=}")
322 339 # ---
  340 + self._fn.fcontext = "pyros_seq"
323 341 param = self._fn.naming_get(seqfile)
324 342 sequence_info['id'] = int(param['id_seq'])
325 343 # --- scientific_program_id is an integer
... ... @@ -342,11 +360,18 @@ class A_Scheduler(Agent):
342 360 sequence_info['visibility_duration'] = visibility_duration # total slots - duration
343 361 sequence_info['duration'] = seq_info['sequence']['duration']
344 362 sequence_info['scientific_program_id'] = scientific_program_id
  363 + sequence_info["period"] = seq_info["sequence"]["period"]
  364 + sequence_info["night_id"] = seq_info["sequence"]["night_id"]
345 365 self.dprint(f" {scientific_program_id=} range to start={len(kobss)}")
346 366 if scientific_program_id not in scientific_program_ids:
347 367 scientific_program_ids.append(scientific_program_id)
348 368 # --- TODO
349   - # update_db_quota_sequence( id_period, night_id, d_total=sequence_info['duration'] )
  369 + quota_attributes = {}
  370 + quota_attributes["d_total"] = int(np.ceil(sequence_info["duration"]))
  371 + quota_attributes["d_schedule"] = int(np.ceil(sequence_info["duration"]))
  372 + quota_attributes["night_id"] = sequence_info["night_id"]
  373 + quota_attributes["id_period"] = sequence_info["period"]
  374 + self.update_db_quota_sequence(seq_info["sequence"]["id"], quota_attributes)
350 375 else:
351 376 sequence_info['error'] = f"File {ephfile} not exists"
352 377 sequence_infos.append(sequence_info)
... ... @@ -544,9 +569,12 @@ class A_Scheduler(Agent):
544 569 log.info(f"_compute_schedule_1 finished in {time.time() - t0:.2f} seconds")
545 570  
546 571 def _create_seq_1(self, nb_seq: int):
  572 + # delete all previous test seq
  573 + Sequence.objects.filter(id__gte=9990000000).delete()
547 574 t0 = time.time()
548 575 self.dprint("Debut _create_seq_1")
549   - seq_template = {'sequence': {'id': 4, 'start_expo_pref': 'IMMEDIATE', 'pyros_user': 2, 'scientific_program': 1, 'name': 'seq_20230628T102140', 'desc': None, 'last_modified_by': 2, 'is_alert': False, 'status': 'TBP', 'with_drift': False, 'priority': None, 'analysis_method': None, 'moon_min': None, 'alt_min': None, 'type': None, 'img_current': None, 'img_total': None, 'not_obs': False, 'obsolete': False, 'processing': False, 'flag': None, 'period': 1, 'start_date': datetime.datetime(2023, 6, 28, 10, 21, 40, tzinfo=zoneinfo.ZoneInfo(key='UTC')), 'end_date': datetime.datetime(2023, 6, 28, 10, 21, 40, 999640, tzinfo=datetime.timezone.utc), 'jd1': Decimal('0E-8'), 'jd2': Decimal('0E-8'), 'tolerance_before': '1s', 'tolerance_after': '1min', 'duration': -1.0, 'overhead': Decimal('0E-8'), 'submitted': False, 'config_attributes': {'tolerance_before': '1s', 'tolerance_after': '1min', 'target': 'RADEC 0H10M -15D', 'conformation': 'WIDE', 'layout': 'Altogether'}, 'ra': None, 'dec': None, 'complete': True, 'night_id': '20230627'}, 'albums': {'Altogether': {'plans': [{'id': 4, 'album': 4, 'duration': 0.0, 'nb_fnges': 1, 'config_attributes': {'binnings': {'binxy': [1, 1], 'readouttime': 6}, 'exposuretime': 1.0}, 'complete': True}]}}}
  576 + seq_template = yaml.safe_load(open("scheduler_seq_template.yml","r"))
  577 + #{"simplified":True, 'sequence': {'id': 4, 'start_expo_pref': 'IMMEDIATE', 'pyros_user': 2, 'scientific_program': 1, 'name': 'seq_20230628T102140', 'desc': None, 'last_modified_by': 2, 'is_alert': False, 'status': 'TBP', 'with_drift': False, 'priority': None, 'analysis_method': None, 'moon_min': None, 'alt_min': None, 'type': None, 'img_current': None, 'img_total': None, 'not_obs': False, 'obsolete': False, 'processing': False, 'flag': None, 'period': 1, 'start_date': datetime.datetime(2023, 6, 28, 10, 21, 40, tzinfo=zoneinfo.ZoneInfo(key='UTC')), 'end_date': datetime.datetime(2023, 6, 28, 10, 21, 40, 999640, tzinfo=datetime.timezone.utc), 'tolerance_before': '1s', 'tolerance_after': '1min', 'duration': -1.0, 'submitted': False, 'config_attributes': {'tolerance_before': '1s', 'tolerance_after': '1min', 'target': 'RADEC 0H10M -15D', 'conformation': 'WIDE', 'layout': 'Altogether'}, 'ra': None, 'dec': None, 'complete': True, 'night_id': '20230627'}, 'albums': {'Altogether': {'plans': [{'id': 4, 'album': 4, 'duration': 0.0, 'nb_fnges': 1, 'config_attributes': {'binnings': {'binxy': [1, 1], 'readouttime': 6}, 'exposuretime': 1.0}, 'complete': True}]}}}
550 578 # decode general variables info a dict info
551 579 info = self.get_infos()
552 580 rootdir = info['rootdir']
... ... @@ -591,35 +619,54 @@ class A_Scheduler(Agent):
591 619 # --- Create new sequences
592 620 for k in range(nb_seq):
593 621 #print("B"*20 + f" {info['operiod'].id} {info['night']} {k}")
  622 + if k < nb_seq/2:
  623 + scientific_program = 0
  624 + else:
  625 + scientific_program = 1
  626 +
594 627 time.sleep(1)
595 628 seq = seq_template.copy()
596   - seq['sequence']['period'] = info['operiod'].id # int
597   - seq['sequence']['night_id'] = info['night'] # str
598   - seq['sequence']['config_attributes']['target'] = k # int
  629 + print(f"{seq}")
  630 + #seq['sequence']['config_attributes']['target'] = k # int
599 631 # ---
600   - start_expo_pref = "BESTELEV" #"IMMEDIATE"
601   - scientific_program = int(k/2)
602   - start_date = datetime.datetime(2023, 6, 28, 10, 21, 40)
603   - end_date = datetime.datetime(2023, 6, 28, 10, 21, 40, 999640, tzinfo=datetime.timezone.utc)
  632 + #start_expo_pref = "BESTELEV" #"IMMEDIATE"
  633 + start_expo_pref = 0 # for bestelev 1, for immediate 0
  634 + start_date,_ = self._fn.night2date(info["night"])
  635 + start_date = start_date + 0.25 + (0.5*k)
  636 + start_date_to_datetime = guitastro.Date(start_date).iso()
  637 + #start_date = datetime.datetime(2023, 6, 28, 10, 21, 40)
  638 + #end_date = datetime.datetime(2023, 6, 28, 10, 21, 40, 999640, tzinfo=datetime.timezone.utc)
604 639 jd1 = Decimal('0E-8')
605 640 jd2 = Decimal('0E-8')
606 641 tolerance_before = '1s'
607   - tolerance_after = '1min'
  642 + tolerance_after = '5min'
608 643 duration = 3000.0
609 644 target = f"RADEC {k}h {10+2*k}d"
610 645 # ---
611 646 seq['sequence']['start_expo_pref'] = start_expo_pref
612 647 seq['sequence']['scientific_program'] = scientific_program
613   - seq['sequence']['start_date'] = start_date
614   - seq['sequence']['end_date'] = end_date
615   - seq['sequence']['jd1'] = jd1
616   - seq['sequence']['jd2'] = jd2
  648 + seq['sequence']['start_date'] = start_date_to_datetime
  649 + # seq['sequence']['jd1'] = jd1
  650 + # seq['sequence']['jd2'] = jd2
617 651 seq['sequence']['tolerance_before'] = tolerance_before
618 652 seq['sequence']['tolerance_after'] = tolerance_after
619   - seq['sequence']['duration'] = duration
620   - seq['sequence']['config_attributes']['target'] = target
  653 + seq['sequence']['target'] = target
621 654 # --- Build the path and file name of the sequence file
622   - fn_param["id_seq"] = int("999" + f"{k:07d}")
  655 + seq["sequence"]["id"] = int("999" + f"{k:07d}")
  656 + seq["sequence"]["name"] = "seq_"+str(seq["sequence"]["id"])
  657 + # Add sequence to db
  658 + # with pyros_api script
  659 + pyros_api = PyrosAPI(None)
  660 + self._fn.fcontext = "pyros_seq_tmp"
  661 + self._fn.extension = ".json"
  662 + seq_fname = self._fn.join(str(seq["sequence"]["id"]))
  663 + seq_file = open(seq_fname,"w")
  664 + seq_file.write(yaml.dump(seq, sort_keys=False))
  665 + seq_file.close()
  666 + response = pyros_api.submit_sequence_file(seq_fname)
  667 + log.info(f"{response}")
  668 +
  669 + """
623 670 self.dprint(f"{k} : {self._fn.fcontext=}")
624 671 self._fn.fname = self._fn.naming_set(fn_param)
625 672 self.dprint(f"{k} : {self._fn.fname=}")
... ... @@ -646,6 +693,7 @@ class A_Scheduler(Agent):
646 693 pickle.dump(seq, open(seq_file,"wb"))
647 694 #dprint(f"{errors=}")
648 695 #dprint("C"*20)
  696 + """
649 697 log.info(f"_create_seq_1 finished in {time.time() - t0:.2f} seconds")
650 698  
651 699 def load_sequence(self):
... ...
src/core/pyros_django/scheduling/scheduler_seq_template.yml 0 โ†’ 100644
... ... @@ -0,0 +1,19 @@
  1 +simplified: True
  2 +sequence:
  3 + scientific_program: 0
  4 + name: seq_20231019T153204
  5 + start_date: '2023-10-19T15:32:04.000000'
  6 + tolerance_before: 1s
  7 + tolerance_after: 1min
  8 + start_expo_pref: 0
  9 + target: RADEC 0H10M -15D
  10 + conformation: 0
  11 + layout: 0
  12 + ALBUMS:
  13 + - Album:
  14 + name: Altogether
  15 + Plans:
  16 + - Plan:
  17 + nb_images: 100
  18 + exposuretime: 10
  19 + binnings: 0
... ...
src/core/pyros_django/scp_mgmt/A_SCP_Manager.py
... ... @@ -19,6 +19,7 @@ for short_path in short_paths:
19 19 from majordome.agent.Agent import Agent, build_agent
20 20 from user_mgmt.models import PyrosUser, Institute, SP_Period, Period, SP_Period, SP_Period_Guest, SP_PeriodWorkflow
21 21 import vendor.guitastro.src.guitastro as guitastro
  22 +from scp_mgmt.models import Quota
22 23  
23 24 # Django imports
24 25 from django.shortcuts import reverse
... ... @@ -47,6 +48,9 @@ class A_SCP_Manager(Agent):
47 48 # Format : โ€œcmd_nameโ€ : (timeout, exec_mode, tooltip)
48 49  
49 50 "do_generate_ephem_moon_and_sun_for_period": (3, Agent.EXEC_MODE.SEQUENTIAL, 'generate ephem of moon & sun for a period'),
  51 + "do_set_quota_for_SP": (60, Agent.EXEC_MODE.SEQUENTIAL, 'set quota for scientific programs for id period'),
  52 + "do_set_quota_for_institutes": (60, Agent.EXEC_MODE.SEQUENTIAL, 'set quota for institutes for id period'),
  53 + "do_run_quota_workflow": (60, Agent.EXEC_MODE.SEQUENTIAL, 'set quota for a period'),
50 54 }
51 55  
52 56 # new init with obsconfig
... ... @@ -58,9 +62,8 @@ class A_SCP_Manager(Agent):
58 62 super().__init__()
59 63 next_period = Period.objects.next_period()
60 64 period = next_period
61   - self.pconfig = self._oc['pyros_config']
62   - self._fn = self.config.fn
63 65 self.config = self._oc['config']
  66 + self._fn = self.config.fn
64 67 self._fn.pathnaming("PyROS.seq.1")
65 68  
66 69 # @override
... ... @@ -235,8 +238,8 @@ class A_SCP_Manager(Agent):
235 238 self.send_mail_to_observers_for_notification(next_sp_to_be_notified)
236 239 SP_PeriodWorkflow.objects.create(period=self.period,action=SP_PeriodWorkflow.NOTIFICATION)
237 240 self.update_sun_moon_ephems()
238   - self.set_quota_for_institutes(self.period.id)
239   - self.set_quota_for_sp(self.period.id)
  241 + self.do_set_quota_for_institutes(self.period.id)
  242 + self.do_set_quota_for_SP(self.period.id)
240 243  
241 244 def routine_process_body(self):
242 245 print("routine automatic period workflow")
... ... @@ -251,27 +254,49 @@ class A_SCP_Manager(Agent):
251 254 for n in range(int((end_date - start_date).days)):
252 255 yield start_date + timedelta(n)
253 256  
254   - def set_quota_for_institutes(self, id_period):
255   - for institute in Institute.objects.all():
256   - quota_f = institute.quota_f
257   - # the lowest id of quota table for this period should be the first night of the period
258   - period_quota = Period.objects.get(id=id_period).quota
259   - institute_quota = period_quota.convert_to_quota(quota_f)
260   - new_quota = Quota()
261   - new_quota.set_attributes_and_save(institute_quota)
262   - institute.quota = new_quota
263   - institute.save()
  257 + def do_run_quota_workflow(self, id_period:int):
  258 + try:
  259 + self.update_sun_moon_ephems()
  260 + except Exception as e:
  261 + print(e)
  262 + self.do_set_quota_for_institutes(id_period)
  263 + self.do_set_quota_for_SP(id_period)
  264 +
  265 + def do_set_quota_for_institutes(self, id_period:int):
  266 + try:
  267 + for institute in Institute.objects.all():
  268 + quota_f = institute.quota_f
  269 + # the lowest id of quota table for this period should be the first night of the period
  270 + period_quota = Period.objects.get(id=id_period).quota
  271 + institute_quota = period_quota.convert_to_quota(quota_f)
  272 + institute_quota["night_id"] = 0
  273 + institute_quota["id_period"] = id_period
  274 + new_quota = Quota()
  275 + new_quota.set_attributes_and_save(institute_quota)
  276 + institute.quota = new_quota
  277 + institute.save()
  278 + except Exception as e:
  279 + print(e)
  280 + raise e
264 281  
265   - def set_quota_for_SP(self, id_period):
266   - period = Period.objects.get(id=id_period)
267   - for sp_period in SP_Period.objects.filter(period=period)
268   - sp = sp_period.scientific_program
269   - institute = sp.institute
270   - institute_quota = institute.quota
271   - new_quota = Quota()
272   - quota_attributes = institute_quota.convert_to_quota(sp.quota_f)
273   - new_quota.set_attributes_and_save(quota_attributes)
274   -
  282 + def do_set_quota_for_SP(self, id_period:int):
  283 + try:
  284 + period = Period.objects.get(id=id_period)
  285 + for sp_period in SP_Period.objects.filter(period=period):
  286 + sp = sp_period.scientific_program
  287 + institute = sp.institute
  288 + institute_quota = institute.quota
  289 + new_quota = Quota()
  290 + quota_attributes = institute_quota.convert_to_quota(sp.quota_f)
  291 + quota_attributes["night_id"] = 1
  292 + quota_attributes["id_period"] = id_period
  293 + new_quota.set_attributes_and_save(quota_attributes)
  294 + sp.quota = new_quota
  295 + sp.save()
  296 +
  297 + except Exception as e:
  298 + print(e)
  299 + raise e
275 300  
276 301 if __name__ == "__main__":
277 302  
... ...
src/core/pyros_django/scp_mgmt/models.py
... ... @@ -4,90 +4,114 @@ from django.db import models
4 4 class Quota(models.Model):
5 5 id_period = models.BigIntegerField(blank=True, null=True)
6 6 night_id = models.CharField(max_length=8, null=True, blank=True, db_index=True)
  7 + d_total = models.BigIntegerField(default=0, blank=True, null=True)
7 8 d_totalq = models.BigIntegerField(default=0, blank=True, null=True)
8 9 d_totalx = models.BigIntegerField(default=0, blank=True, null=True)
9 10  
  11 + d_previous = models.BigIntegerField(default=0, blank=True, null=True)
10 12 d_previousq = models.BigIntegerField(default=0, blank=True, null=True)
11 13 d_previousx = models.BigIntegerField(default=0, blank=True, null=True)
12 14  
  15 + d_current = models.BigIntegerField(default=0, blank=True, null=True)
13 16 d_currentq = models.BigIntegerField(default=0, blank=True, null=True)
14 17 d_currentx = models.BigIntegerField(default=0, blank=True, null=True)
15 18  
  19 + d_passed = models.BigIntegerField(default=0, blank=True, null=True)
16 20 d_passedq = models.BigIntegerField(default=0, blank=True, null=True)
17 21 d_passedx = models.BigIntegerField(default=0, blank=True, null=True)
18 22  
  23 + d_schedule = models.BigIntegerField(default=0, blank=True, null=True)
19 24 d_scheduleq = models.BigIntegerField(default=0, blank=True, null=True)
20 25 d_schedulex = models.BigIntegerField(default=0, blank=True, null=True)
21 26  
  27 + d_next = models.BigIntegerField(default=0, blank=True, null=True)
22 28 d_nextq = models.BigIntegerField(default=0, blank=True, null=True)
23 29 d_nextx = models.BigIntegerField(default=0, blank=True, null=True)
24 30  
25   - @property
26   - def d_total(self):
27   - return self.d_totalq + self.d_totalx
  31 + # @property
  32 + # def d_total(self):
  33 + # return self.d_totalq + self.d_totalx
28 34  
29   - @property
30   - def d_previous(self):
31   - return self.d_previousq + self.d_previousx
  35 + # @property
  36 + # def d_previous(self):
  37 + # return self.d_previousq + self.d_previousx
32 38  
33   - @property
34   - def d_current(self):
35   - return self.d_currentq + self.d_currentx
  39 + # @property
  40 + # def d_current(self):
  41 + # return self.d_currentq + self.d_currentx
36 42  
37   - @property
38   - def d_passed(self):
39   - return self.d_passedq + self.d_passedx
  43 + # @property
  44 + # def d_passed(self):
  45 + # return self.d_passedq + self.d_passedx
40 46  
41   - @property
42   - def d_schedule(self):
43   - return self.d_scheduleq + self.d_schedulex
  47 + # @property
  48 + # def d_schedule(self):
  49 + # return self.d_scheduleq + self.d_schedulex
44 50  
45   - @property
46   - def d_next(self):
47   - return self.d_nextq + self.d_nextx
  51 + # @property
  52 + # def d_next(self):
  53 + # return self.d_nextq + self.d_nextx
48 54  
49 55 def set_attributes_and_save(self, quota_attributes:dict):
50   -
51 56 if quota_attributes.get("id_period") != None:
52 57 self.id_period = quota_attributes["id_period"]
53 58 if quota_attributes.get("night_id") != None:
54 59 self.night_id = quota_attributes["night_id"]
  60 +
55 61 if quota_attributes.get("d_totalq") != None:
56 62 self.d_totalq = quota_attributes["d_totalq"]
57 63 if quota_attributes.get("d_totalx") != None:
58 64 self.d_totalx = quota_attributes["d_totalx"]
  65 +
59 66 if quota_attributes.get("d_previousq") != None:
60 67 self.d_previousq = quota_attributes["d_previousq"]
61 68 if quota_attributes.get("d_previousx") != None:
62 69 self.d_previousx = quota_attributes["d_previousx"]
  70 +
63 71 if quota_attributes.get("d_currentq") != None:
64 72 self.d_currentq = quota_attributes["d_currentq"]
65 73 if quota_attributes.get("d_currentx") != None:
66 74 self.d_currentx = quota_attributes["d_currentx"]
  75 +
  76 + if quota_attributes.get("d_passedq") != None:
  77 + self.d_passedq = quota_attributes["d_passedq"]
  78 + if quota_attributes.get("d_passedx") != None:
  79 + self.d_passedx = quota_attributes["d_passedx"]
  80 +
  81 +
67 82 if quota_attributes.get("d_scheduleq") != None:
68 83 self.d_scheduleq = quota_attributes["d_scheduleq"]
69 84 if quota_attributes.get("d_schedulex") != None:
70   - self.d_schedule = quota_attributes["d_schedulex"]
  85 + self.d_schedulex = quota_attributes["d_schedulex"]
  86 +
  87 + if quota_attributes.get("d_nextx") != None:
  88 + self.d_nextq = quota_attributes["d_nextx"]
71 89 if quota_attributes.get("d_nextq") != None:
72 90 self.d_nextq = quota_attributes["d_nextq"]
73   - if quota_attributes.get("d_nextx") != None:
74   - self.d_nextx = quota_attributes["d_nextx"]
  91 +
  92 +
  93 + if quota_attributes.get("d_total") != None:
  94 + self.d_total = quota_attributes["d_total"]
  95 + if quota_attributes.get("d_previous") != None:
  96 + self.d_previous = quota_attributes["d_previous"]
  97 + if quota_attributes.get("d_current") != None:
  98 + self.d_current = quota_attributes["d_current"]
  99 + if quota_attributes.get("d_passed") != None:
  100 + self.d_passed = quota_attributes["d_passed"]
  101 + if quota_attributes.get("d_schedule") != None:
  102 + self.d_schedule = quota_attributes["d_schedule"]
  103 + if quota_attributes.get("d_next") != None:
  104 + self.d_next = quota_attributes["d_next"]
75 105 self.save()
76 106  
77 107 def convert_to_quota(self, quota_f):
78 108 quota_institute = {}
79 109  
80   - quota_institute["d_totalq"] = self.d_totalq * quota_f
81   - quota_institute["d_totalx"] = self.d_totalx * quota_f
82   - quota_institute["d_previousq"] = self.d_previousq * quota_f
83   - quota_institute["d_previousx"] = self.d_previousx * quota_f
84   - quota_institute["d_currentq"] = self.d_currentq * quota_f
85   - quota_institute["d_currentx"] = self.d_currentx * quota_f
86   - quota_institute["d_passedq"] = self.d_passedq * quota_f
87   - quota_institute["d_passedx"] = self.d_passedx * quota_f
88   - quota_institute["d_scheduleq"] = self.d_scheduleq * quota_f
89   - quota_institute["d_schedulex"] = self.d_schedulex * quota_f
90   - quota_institute["d_nextq"] = self.d_nextq * quota_f
91   - quota_institute["d_nextx"] = self.d_nextx * quota_f
  110 + quota_institute["d_total"] = self.d_total * quota_f
  111 + quota_institute["d_previous"] = self.d_previous * quota_f
  112 + quota_institute["d_current"] = self.d_current * quota_f
  113 + quota_institute["d_passed"] = self.d_passed * quota_f
  114 + quota_institute["d_schedule"] = self.d_schedule * quota_f
  115 + quota_institute["d_next"] = self.d_next * quota_f
92 116  
93 117 return quota_institute
... ...
src/core/pyros_django/scp_mgmt/templates/scp_mgmt/quota_sp.html 0 โ†’ 100644
... ... @@ -0,0 +1,128 @@
  1 +{% extends "base.html" %}
  2 +
  3 +{% load tags %}
  4 +{% block content %}
  5 +
  6 +<style>
  7 + .institute{
  8 + background-color: aqua;
  9 + }
  10 + .sp{
  11 + background-color: aquamarine;
  12 + }
  13 +</style>
  14 +<table class="table table-sm table-bordered tablesorter">
  15 + <thead>
  16 + <tr>
  17 + <td> name </td>
  18 + <td> quota_f </td>
  19 + <td> <b> d_total </b> </td>
  20 + <td> d_totalq </td>
  21 + <td> d_totalx </td>
  22 + <td> <b> d_previous </b> </td>
  23 + <td> d_previousq </td>
  24 + <td> d_previousx </td>
  25 + <td> <b> d_current </b> </td>
  26 + <td> d_currentq </td>
  27 + <td> d_currentx </td>
  28 + <td> <b> d_passed </b> </td>
  29 + <td> d_passedq </td>
  30 + <td> d_passedx </td>
  31 + <td> <b> d_schedule </b> </td>
  32 + <td> d_scheduleq </td>
  33 + <td> d_schedulex </td>
  34 + <td> <b> d_next </b> </td>
  35 + <td> d_nextq </td>
  36 + <td> d_nextx </td>
  37 + </tr>
  38 + </thead>
  39 + <tr class="current_night">
  40 + <td> Current night : {{ current_night }} </td>
  41 + <td> </td>
  42 + <td> <b>{{quota_current_night.d_total}}</b> </td>
  43 + <td>{{quota_current_night.d_totalq}}</td>
  44 + <td>{{quota_current_night.d_totalx}}</td>
  45 +
  46 + <td> <b>{{quota_current_night.d_previous}}</b> </td>
  47 + <td>{{quota_current_night.d_previousq}}</td>
  48 + <td>{{quota_current_night.d_previousx}}</td>
  49 +
  50 + <td> <b>{{quota_current_night.d_current}}</b> </td>
  51 + <td>{{quota_current_night.d_currentq}}</td>
  52 + <td>{{quota_current_night.d_currentx}}</td>
  53 +
  54 + <td> <b>{{quota_current_night.d_passed}}</b> </td>
  55 + <td>{{quota_current_night.d_passedq}}</td>
  56 + <td>{{quota_current_night.d_passedx}}</td>
  57 +
  58 + <td> <b>{{quota_current_night.d_schedule}}</b> </td>
  59 + <td>{{quota_current_night.d_scheduleq}}</td>
  60 + <td>{{quota_current_night.d_schedulex}} </td>
  61 +
  62 + <td> <b>{{quota_current_night.d_next}}</b> </td>
  63 + <td>{{quota_current_night.d_nextq}}</td>
  64 + <td>{{quota_current_night.d_nextx}}</td>
  65 + </tr>
  66 + {% for institute in institutes %}
  67 + <tr class="institute">
  68 + <td> Institute :{{institute}} </td>
  69 + <td>{{institute.quota_f}} </td>
  70 + <td> <b>{{institute.quota.d_total}}</b> </td>
  71 + <td>{{institute.quota.d_totalq}}</td>
  72 + <td>{{institute.quota.d_totalx}}</td>
  73 +
  74 + <td> <b>{{institute.quota.d_previous}}</b> </td>
  75 + <td>{{institute.quota.d_previousq}}</td>
  76 + <td>{{institute.quota.d_previousx}}</td>
  77 +
  78 + <td> <b>{{institute.quota.d_current}}</b> </td>
  79 + <td>{{institute.quota.d_currentq}}</td>
  80 + <td>{{institute.quota.d_currentx}}</td>
  81 +
  82 + <td> <b>{{institute.quota.d_passed}}</b> </td>
  83 + <td>{{institute.quota.d_passedq}}</td>
  84 + <td>{{institute.quota.d_passedx}}</td>
  85 +
  86 + <td> <b>{{institute.quota.d_schedule}}</b> </td>
  87 + <td>{{institute.quota.d_scheduleq}}</td>
  88 + <td>{{institute.quota.d_schedulex}} </td>
  89 +
  90 + <td> <b>{{institute.quota.d_next}}</b> </td>
  91 + <td>{{institute.quota.d_nextq}}</td>
  92 + <td>{{institute.quota.d_nextx}}</td>
  93 + </tr>
  94 + {% for sp in institute.scientific_programs.all %}
  95 + <tr class="sp">
  96 + <td>Scientific program: {{sp.name}} </td>
  97 + <td>{{sp.quota_f}} </td>
  98 + <td> <b>{{sp.quota.d_total}}</b> </td>
  99 + <td>{{sp.quota.d_totalq}}</td>
  100 + <td>{{sp.quota.d_totalx}}</td>
  101 +
  102 + <td> <b>{{sp.quota.d_previous}}</b> </td>
  103 + <td>{{sp.quota.d_previousq}}</td>
  104 + <td>{{sp.quota.d_previousx}}</td>
  105 +
  106 + <td> <b>{{sp.quota.d_current}}</b> </td>
  107 + <td>{{sp.quota.d_currentq}}</td>
  108 + <td>{{sp.quota.d_currentx}}</td>
  109 +
  110 + <td> <b>{{sp.quota.d_passed}}</b> </td>
  111 + <td>{{sp.quota.d_passedq}}</td>
  112 + <td>{{sp.quota.d_passedx}}</td>
  113 +
  114 + <td> <b>{{sp.quota.d_schedule}}</b> </td>
  115 + <td>{{sp.quota.d_scheduleq}}</td>
  116 + <td>{{sp.quota.d_schedulex}} </td>
  117 +
  118 + <td> <b>{{sp.quota.d_next}}</b> </td>
  119 + <td>{{sp.quota.d_nextq}}</td>
  120 + <td>{{sp.quota.d_nextx}}</td>
  121 + </tr>
  122 + {% endfor %}
  123 +
  124 + {% endfor %}
  125 +
  126 +
  127 +
  128 +{% endblock %}
... ...
src/core/pyros_django/scp_mgmt/urls.py
... ... @@ -43,5 +43,7 @@ urlpatterns = [
43 43 path("detail_science_theme/<int:id>/",views.detail_science_theme,name="detail_science_theme"),
44 44 path("edit_science_theme/<int:id>/",views.edit_science_theme,name="edit_science_theme"),
45 45 path("delete_science_theme/<int:id>/",views.delete_science_theme,name="delete_science_theme"),
46   - path("test_tac_auto",views.test_tac_auto,name="test_tac_auto")
  46 + path("test_tac_auto",views.test_tac_auto,name="test_tac_auto"),
  47 + # quota
  48 + path("quota_sp",views.quota_sp,name="quota_sp")
47 49 ]
... ...
src/core/pyros_django/scp_mgmt/views.py
... ... @@ -2,7 +2,7 @@
2 2 from datetime import date, datetime
3 3 from dateutil.relativedelta import relativedelta
4 4 import matplotlib.pyplot as plt
5   -import re,io,urllib,base64
  5 +import re,io,urllib,base64, os
6 6  
7 7 # Django imports
8 8 from django.http.response import HttpResponse
... ... @@ -22,12 +22,12 @@ from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
22 22 from dashboard.config_pyros import ConfigPyros
23 23 from .functions import get_global_svg_timeline, get_svg_timeline, get_proposal_svg_timeline
24 24 from user_mgmt.models import ScientificProgram, Institute, Period, SP_Period_User, SP_Period, PyrosUser, SP_Period_Guest, ScienceTheme #, UserLevel
25   -from seq_submit.models import Sequence
  25 +from seq_submit.models import Sequence, Quota
26 26 #from src.core.pyros_django import scientific_program
27 27 from .forms import PeriodForm, ScienceThemeForm, ScientificProgramForm, InstituteForm, SP_PeriodForm,TACAssociationForm
28 28 from src.core.pyros_django.dashboard.decorator import level_required
29 29  
30   -
  30 +from src.core.pyros_django.obs_config.obsconfig_class import OBSConfig
31 31  
32 32  
33 33  
... ... @@ -807,6 +807,19 @@ def institute_list(request):
807 807 })
808 808  
809 809  
  810 +@login_required
  811 +@level_required("Admin", "Unit-PI", "Observer", "Operator", "TAC", "Management board member")
  812 +def quota_sp(request):
  813 + institutes = Institute.objects.all()
  814 + scientific_programs = ScientificProgram.objects.all()
  815 + config = OBSConfig(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
  816 + current_night = config.fn.date2night("now")
  817 + current_period = Period.objects.exploitation_period()
  818 + # lowest id is period line
  819 + quota_current_night = Quota.objects.filter(id_period=current_period.id, night_id=current_night).order_by("id").first()
  820 + return render(request, 'scp_mgmt/quota_sp.html', locals())
  821 +
  822 +
810 823 # Exploitation Periods CRUD
811 824  
812 825 @login_required
... ...
src/core/pyros_django/seq_submit/functions.py
... ... @@ -164,6 +164,17 @@ def check_sequence_file_validity_and_save(yaml_content: dict, request: HttpReque
164 164 if Period.objects.next_period() != None and Period.objects.next_period().start_date < seq.start_date.date():
165 165 period = Period.objects.next_period()
166 166 seq.period = period
  167 + # Sum seq duration
  168 + duration = 0
  169 + max_duration = 0
  170 + for album in seq.albums.all():
  171 + for plan in album.plans.all():
  172 + duration = plan.nb_images * (plan.config_attributes.get("exposuretime",0) + plan.config_attributes.get("readouttime",0))
  173 + plan.duration = duration
  174 + plan.save()
  175 + if duration >= max_duration:
  176 + max_duration = duration
  177 + seq.duration = max_duration
167 178 fn = guitastro.FileNames()
168 179 home = config.getHome()
169 180 guitastro_home = guitastro.Home(home)
... ... @@ -316,6 +327,9 @@ def process_sequence(yaml_content, seq, config, is_simplified, result, user_sp):
316 327  
317 328 if is_simplified:
318 329 seq.scientific_program = sp_list[yaml_content["sequence"]["scientific_program"]]
  330 + if yaml_content["sequence"].get("id"):
  331 + seq.id = yaml_content["sequence"].get("id")
  332 + seq.save()
319 333 else:
320 334 # get scientific program field's attributes
321 335 yaml_seq_sp = yaml_content["sequence"]["scientific_program"]
... ... @@ -416,7 +430,6 @@ def process_sequence(yaml_content, seq, config, is_simplified, result, user_sp):
416 430 # else associate field & value in config_attributes sequence's field (JsonField) = variable fields of an sequence
417 431 seq.config_attributes[field] = value
418 432  
419   -
420 433 def create_sequence_pickle(sequence):
421 434 seq_dict = model_to_dict(sequence)
422 435 fullseq_dict = {
... ... @@ -443,8 +456,6 @@ def create_sequence_pickle(sequence):
443 456 config = OBSConfig(os.environ["PATH_TO_OBSCONF_FILE"], unit_name)
444 457 pyros_config = ConfigPyros(os.environ["pyros_config_file"])
445 458 config.fn.fcontext = "pyros_seq"
446   - # home = guitastro.Home(config.getHome())
447   - # config.fn.longitude = home.longitude
448 459 period_id = str(period.id)
449 460 if len(str(period.id)) < 3:
450 461 while len(period_id) < 3:
... ... @@ -456,6 +467,12 @@ def create_sequence_pickle(sequence):
456 467 "date": sequence.night_id,
457 468 "id_seq": sequence.id
458 469 }
  470 + test_mode = False
  471 + if sequence.id >= 9990000000:
  472 + # in test mode
  473 + config.fn.rootdir = os.path.abspath(config.fn.rootdir.replace("PRODUCTS/","PRODUCTS/TESTS/", 1))
  474 + test_mode = True
  475 +
459 476 config.fn.fname = config.fn.naming_set(fn_param)
460 477 fpath_name = config.fn.join(config.fn.fname)
461 478 # create dirs if they don't exist
... ... @@ -467,9 +484,22 @@ def create_sequence_pickle(sequence):
467 484 duskelev = -7
468 485 errors = []
469 486 try:
470   - # TODO remplacer les none par les fichiers pickle de ephem_sun & ephem_moon
471 487 #fullseq_dict["ephem"] = eph.target2night(fullseq_dict["sequence"]["config_attributes"]["target"], sequence.night_id, None, None, preferance=sequence.start_expo_pref, duskelev=duskelev)
472   - ephem = eph.target2night(fullseq_dict["sequence"]["config_attributes"]["target"], sequence.night_id, None, None, preference=sequence.start_expo_pref, duskelev=duskelev)
  488 + # change fcontext to eph context
  489 + config.fn.fcontext = "pyros_eph"
  490 + if test_mode:
  491 + config.fn.rootdir = os.path.abspath(config.fn.rootdir.replace("PRODUCTS/","PRODUCTS/TESTS/", 1))
  492 + eph_root_dir = config.fn.rootdir
  493 + fn_param["target"] = "sun"
  494 + config.fn.fname = config.fn.naming_set(fn_param)
  495 + sun_eph_fpath = config.fn.join(config.fn.fname)
  496 + fn_param["target"] = "moon"
  497 + config.fn.fname = config.fn.naming_set(fn_param)
  498 + moon_eph_fpath = config.fn.join(config.fn.fname)
  499 + # open eph files
  500 + sun_eph = pickle.load(open(sun_eph_fpath,"rb"))
  501 + moon_eph = pickle.load(open(moon_eph_fpath,"rb"))
  502 + ephem = eph.target2night(fullseq_dict["sequence"]["config_attributes"]["target"], sequence.night_id, sun_eph, moon_eph, preference=sequence.start_expo_pref, duskelev=duskelev)
473 503 except ValueError:
474 504 errors.append("Target value is not valid")
475 505 except guitastro.ephemeris.EphemerisException as ephemException:
... ...
src/core/pyros_django/seq_submit/models.py
... ... @@ -352,7 +352,7 @@ class Sequence(models.Model):
352 352 flag = models.CharField(max_length=45, blank=True, null=True)
353 353 period = models.ForeignKey(Period, on_delete=models.DO_NOTHING, related_name="sequence_period", blank=True, null=True)
354 354 #period = models.ForeignKey("Period", on_delete=models.DO_NOTHING, related_name="sequences", blank=True, null=True)
355   - quota = models.ForeignKey(Quota, on_delete=models.DO_NOTHING,related_name="sequence_quotas", blank=True, null=True)
  355 + quota = models.ForeignKey(Quota, on_delete=models.SET_NULL,related_name="sequence_quotas", blank=True, null=True)
356 356  
357 357 start_date = models.DateTimeField(
358 358 blank=True, null=True, default=timezone.now, editable=True)
... ...
src/core/pyros_django/user_mgmt/models.py
... ... @@ -87,7 +87,7 @@ class Institute(models.Model):
87 87 quota_f = models.FloatField(
88 88 validators=[MinValueValidator(0), MaxValueValidator(1)], blank=True, null=True)
89 89  
90   - quota = models.ForeignKey(Quota, on_delete=models.DO_NOTHING,related_name="institute_quotas", blank=True, null=True)
  90 + quota = models.ForeignKey(Quota, on_delete=models.SET_NULL,related_name="institute_quotas", blank=True, null=True)
91 91 #representative_user = models.ForeignKey("PyrosUser", on_delete=models.DO_NOTHING,related_name="institutes",default=1)
92 92  
93 93 def __str__(self) -> str:
... ... @@ -375,7 +375,7 @@ class Period(models.Model):
375 375 data_accessibility_duration = models.PositiveIntegerField(
376 376 blank=True, null=True, default=365*10, editable=True)
377 377  
378   - quota = models.ForeignKey(Quota, on_delete=models.DO_NOTHING,related_name="period_quotas", blank=True, null=True)
  378 + quota = models.ForeignKey(Quota, on_delete=models.SET_NULL,related_name="period_quotas", blank=True, null=True)
379 379  
380 380 @property
381 381 def end_date(self):
... ... @@ -475,7 +475,7 @@ class ScientificProgram(models.Model):
475 475 science_theme = models.ForeignKey(ScienceTheme, on_delete=models.DO_NOTHING, related_name="scientific_program_theme", default=1)
476 476 is_auto_validated = models.BooleanField(default=False)
477 477 objects = ScientificProgramManager()
478   - quota = models.ForeignKey(Quota, on_delete=models.DO_NOTHING, related_name="scientific_program_quotas", blank=True, null=True)
  478 + quota = models.ForeignKey(Quota, on_delete=models.SET_NULL, related_name="scientific_program_quotas", blank=True, null=True)
479 479 quota_f = models.FloatField(
480 480 validators=[MinValueValidator(0), MaxValueValidator(1)], blank=True, null=True)
481 481  
... ...