Commit 9a4dbcf2c2ec2afb2547b717e24e1a753a75f3c6
Exists in
dev
Merge branch 'dev' of https://gitlab.irap.omp.eu/pyros-irap/pyros into dev
Showing
19 changed files
with
622 additions
and
130 deletions
Show diff stats
.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
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
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 | ... | ... |