Source code for lst_auto_rta.observation_data

from typing import Annotated, NamedTuple

import pymongo
import pymongo.errors
from annotated_types import Ge, Gt, Le, Lt


[docs] class ObsInfo(NamedTuple): obs_id: Annotated[int, Gt(0)] | None time_start_camera: Annotated[float, Gt(0)] | None RA: Annotated[float, Ge(0.0), Le(360.0)] | None DEC: Annotated[float, Ge(-90.0), Le(90.0)] | None source_RA: Annotated[float, Ge(0.0), Le(360.0)] | None source_DEC: Annotated[float, Ge(-90.0), Le(90.0)] | None
[docs] def get_current_run_info( db_hostname: str, obs_target_query_timeout_s: Annotated[int, Gt(0), Lt(60)], ) -> ObsInfo: """Queries the LST database for the current observation and observation parameters Parameters ---------- db_hostname : str Hostname of the DB server. Example lst101 obs_target_query_timeout_s : Annotated[int, Gt(0), Lt(60)] Maximal amount of time passed to query DB for observation parameters Raises ------ pymongo.errors.ExecutionTimeout When the database query exceeds the timeout. pymongo.errors.PyMongoError When the database query returned inconsistent results between data.camera and data.structure fields Returns ------- ObsInfo Current observation information """ # The "lst1_obs_summary", DB is filled by LST sub-systems with the information they have/use, when they "want" # The observation information can be retrieved from the "telescope" collection which is updated with # camera and structure data, after a run a started (so we get a delay until the information is available) # For the record, the "camera" collection is updated at the end of a run. # There can be an issue with the values retrieved from the DB concerning the run_number: the run number # is decided by the EVB, and communicating it to the TCU that writes the DB can take longer than the TCU # is willing to wait. When this happens, the TCU writes the previous run_number, so there can be duplicated # run_number in the DB, and the run_number can be wrong. # In addition, the pointing information is not written in the same array than the run_number, therefore we # must query for 2 different pieces of the documents. # Also, the DB documents contain information for muliple runs (for a complete set of wobbles). # To circumvent these issues, we do the following: # - query the DB and sort (descending) on the tstart field of both data.structure and data.camera # - limit the query to 1 element, because otherwise the sort takes to much memory. # Doing so allows the sort to only keep in memory the max value # https://www.mongodb.com/docs/manual/reference/operator/aggregation/sort/#-sort-operator-and-memory # - sort the retrieved documents by tstart (sorting the info of each wobble run) # - assert the two documents are coherent (simply check length). We can't compare tstart because the # tstart of camera is the time at which the camera start, while tstart of structure is the time at which # the drive started tracking, they can be different. # - assign the observation information with the most recent values. # - iterate on the previous runs information and check that the run_number is different # (in inner for loop: iterate on wobbles, in outer while loop: iterate over previous wobble sets) # If the run_number is different, or obs_id is not duplicated, we can quit. Otherwise, we increment the # value by which we need to shift the obs_id. # - If a document is inserted in the DB while we are iterating, we will get the same "_id" again, so we can # simply ignore it. obs_id = None tstart_camera = None ra = None dec = None source_ra = None source_dec = None with pymongo.MongoClient(db_hostname) as client: collection = client.get_database("lst1_obs_summary").get_collection("telescope") camera_data_index = [("data.camera.tstart", pymongo.DESCENDING)] keep_querying = True query_idx = 0 visited_ids = set() nb_duplicated_run_numbers = 0 obs_info = ObsInfo(None, None, None, None, None, None) while keep_querying: with collection.find( { "data.camera.run_number": {"$exists": True}, "data.camera.tstart": {"$exists": True}, "data.structure.tstart": {"$exists": True}, "data.structure.target.ra": {"$exists": True}, "data.structure.target.dec": {"$exists": True}, "data.structure.target.source_ra": {"$exists": True}, "data.structure.target.source_dec": {"$exists": True}, }, { "_id": True, "data.camera.run_number": True, "data.camera.tstart": True, "data.structure.tstart": True, "data.structure.target.ra": True, "data.structure.target.dec": True, "data.structure.target.source_ra": True, "data.structure.target.source_dec": True, }, sort=camera_data_index, max_time_ms=obs_target_query_timeout_s * 1000, skip=query_idx, limit=1, ) as cursor: query_data = cursor.next() if query_data["_id"] not in visited_ids: visited_ids.add(query_data["_id"]) structure_list = sorted(query_data["data"]["structure"], key=lambda x: x["tstart"], reverse=True) camera_list = sorted(query_data["data"]["camera"], key=lambda x: x["tstart"], reverse=True) # if the DB is incoherent for the latest run: raise error. # Otherwise do not, and pray the incoherence is not caused by camera field... if (len(structure_list) != len(camera_list)) and (query_idx == 0): # Raise pymongo error because raise pymongo.errors.PyMongoError( "TCU DB query returned inconsistent data.structure (length {}) " "and data.camera (length {})".format(len(structure_list), len(camera_list)) ) if query_idx == 0: obs_id = camera_list[0]["run_number"] tstart_camera = camera_list[0]["tstart"] ra = structure_list[0]["target"]["ra"] dec = structure_list[0]["target"]["dec"] source_ra = structure_list[0]["target"]["source_ra"] source_dec = structure_list[0]["target"]["source_dec"] comparison_start_idx = 1 if query_idx == 0 else 0 for field in camera_list[comparison_start_idx:]: if field["run_number"] != obs_info.obs_id: keep_querying = False break else: nb_duplicated_run_numbers += 1 query_idx += 1 return ObsInfo(obs_id + nb_duplicated_run_numbers, tstart_camera, ra, dec, source_ra, source_dec)