def solar_avoidance_degrees(self): """ Returns 0.0 if there is no solar avoidance, otherwise returns this obs param in degrees. """ sa = self.get_solar_avoidance() return 0.0 if sa is None else TimeAgent.rad2deg(sa)
def _get_future_maintenance_dates3(): today = TimeAgent.truncateDt(datetime.now()) mp = Period.objects\ .filter(session__observing_type__type = "maintenance")\ .latest("start") last_date = mp.start week = today - timedelta(today.weekday()) # get the date of the Monday of this week. dates = {} # now loop, one week at a time, until that last date, gathering # all the maintenance periods. Each group will be represented as # a day of the week: 'A' = 0 (Monday), 'B' = 1 (Tuesday), etc. # These dates are then entered into the list of possible future # dates. while week < last_date: groups = Maintenance_Activity_Group.get_maintenance_activity_groups(week) for i in groups: d = str(i.get_start().date()) if not dates.has_key(d): dates[d] = [] dates[d].append(str(i.rank)) week += timedelta(7) return dates
def end(self): end = self.contained.period.end() end = TimeAgent.utc2est(end) if self.TZ == 'ET' else end end = datetime(end.year, end.month, end.day, 0, 0) + timedelta(days = 1)\ if self.end_cutoff else end return end
def get_due_date(template): if template.repeat_interval == 30: due_dates = get_monthly_due_dates(template) for i in range(0, 7): dday = self.week + timedelta(days = i) if dday in due_dates: return dday if template.repeat_interval == 7: week = TimeAgent.truncateDt(self.week) start_date = TimeAgent.truncateDt(template.get_start()) diff = timedelta(days = (week - start_date).days % \ template.repeat_interval) return week + timedelta(7) - diff return None
def good_fit(template, mag): # Checks to see if this template wouldn't work better # elsewhere. If so, returns False. If not, returns True. # first, take care of simple cases: repeat = 1, or no # published mags this week, the template is due, and this # is the highest U: if template.repeat_interval == 1: return False if better_fit(t, other_groups_today) else True if len(published_groups_this_week) == 0 \ and is_highest_U(mag): return True if is_P(self): # Here we generate a dictionary to weigh the possible dates that a # repeat event can be substantiated in. The lower the number the better. # A slight preference is given to the date that comes before the due date # over one that comes after, but that's really just for tie-breaking purposes. #dm = {-4: 40, -3: 30, -2: 20, -1: 10, 0: 0, 1: 15, 2: 25, 3: 35, 4: 45} dayLen = 30 dm = [(i,10*abs(i)) for i in range(-dayLen,1)] dm.extend([(i,(10*i)+5) for i in range(1,dayLen)]) dm = dict(dm) today = TimeAgent.truncateDt(self.period.start) p = [mag.period for mag in published_groups_this_week] due_date = get_due_date(template) diff = (today - due_date).days if diff: # doesn't fall on this date. Is this the closest # period though? for j in p: if j != self.period: # check only other periods mod = (TimeAgent.truncateDt(j.start) - due_date).days # Test to see if it's a better fit in # another period. and if so, don't # use here. if dm[mod] < dm[diff]: return False return True return False
def test_getSchedulingRange(self): # scheduling range is 0:00 of timezone of first day # to the last day at 8:00 EST # test it in winter time dt = datetime(2010, 1, 1) days = 2 expStart = datetime(2010, 1, 1, 0) expEnd = datetime(2010, 1, 3, 13) expDur = TimeAgent.dtDiffMins(expStart, expEnd) start, dur = ScheduleTools().getSchedulingRange(dt, 'UTC', days) self.assertEquals(expStart, start) self.assertEquals(expDur, dur) # make sure it works in ET too expStart = datetime(2010, 1, 1, 5) expEnd = datetime(2010, 1, 3, 13) expDur = TimeAgent.dtDiffMins(expStart, expEnd) start, dur = ScheduleTools().getSchedulingRange(dt, 'ET', days) self.assertEquals(expStart, start) # test it in summer time dt = datetime(2010, 6, 10) days = 3 expStart = datetime(2010, 6, 10, 0) expEnd = datetime(2010, 6, 13, 12) expDur = TimeAgent.dtDiffMins(expStart, expEnd) start, dur = ScheduleTools().getSchedulingRange(dt, 'UTC', days) self.assertEquals(expStart, start) self.assertEquals(expDur, dur) # make sure it works in ET too expStart = datetime(2010, 6, 10, 4) expEnd = datetime(2010, 6, 13, 12) expDur = TimeAgent.dtDiffMins(expStart, expEnd) start, dur = ScheduleTools().getSchedulingRange(dt, 'ET', days) self.assertEquals(expStart, start) self.assertEquals(expDur, dur)
def start(self): start = self.contained.period.start if self.TZ == 'ET': start = TimeAgent.utc2est(start) if self.start_cutoff: start = datetime(start.year, start.month, start.day) + timedelta(days = 1) return start
def start(self): w = self.contained.get_week() # assume start time of 8:00 ET, to be converted to UT below, # if necessary. dt = datetime(w.year, w.month, w.day, 8) if self.TZ != 'ET': dt = TimeAgent.est2utc(dt) return dt
def get_end(self, tzname = None): """ If this is a fixed maintenance period then return the period's end, if not return the end of the week. """ if self.period: end = self.period.end() return TimeAgent.utc2est(end) if tzname == 'ET' else end else: return self.get_week() + timedelta(7)
def get_start(self, tzname = None): """ If this is a fixed maintenance period there will have been a period assigned. If so, return this period's start. If not, return the start-of-week date. """ if self.period: start = self.period.start return TimeAgent.utc2est(start) if tzname == 'ET' else start else: return self.get_week()
def set_base_fields(self, fdata): fsestype = fdata.get("type", "open") fobstype = fdata.get("science", "testing") proj_code = fdata.get("pcode", "GBT09A-001") try: p = Project.objects.get(pcode = proj_code) except Project.DoesNotExist: p = Project.objects.all()[0] self.sesshun.project = p self.sesshun.session_type = Session_Type.objects.get(type = fsestype) self.sesshun.observing_type = Observing_Type.objects.get(type = fobstype) self.sesshun.original_id = \ self.get_field(fdata, "orig_ID", None, lambda x: int(float(x))) self.sesshun.name = fdata.get("name", None) self.sesshun.frequency = fdata.get("freq", None) self.sesshun.max_duration = TimeAgent.rndHr2Qtr(float(fdata.get("req_max", 12.0))) self.sesshun.min_duration = TimeAgent.rndHr2Qtr(float(fdata.get("req_min", 3.0))) self.sesshun.time_between = fdata.get("between", None)
def jsondict(self, tz): start = self.period.start if tz == 'UTC' else TimeAgent.utc2est(self.period.start) end = self.period.end() if tz == 'UTC' else TimeAgent.utc2est(self.period.end()) w = self.period.window # stakeholder want's 'T' or '' for this sponsored = self.period.session.project.is_sponsored() sponsored = '' if not sponsored else 'T' js = {"id" : self.period.id , "session" : SessionHttpAdapter(self.period.session).jsondict() , "session_name" : self.period.session.name , "handle" : self.period.toHandle() , "stype" : self.period.session.session_type.type[0].swapcase() , "end_date" : d2str(end) , "end_time" : t2str(end) , "date" : d2str(start) , "time" : t2str(start) , "lst" : str(TimeAgent.dt2tlst(self.period.start)) , "duration" : self.period.duration , "sscore" : self.period.score # scheduling score , "cscore" : -1.0 # current score , "forecast" : dt2str(self.period.forecast) , "backup" : self.period.backup , "moc_ack" : self.period.moc_ack if self.period.moc_ack is not None else False , "state" : self.period.state.abbreviation if self.period.state is not None else "" , "windowed" : True if w is not None else False , "wdefault" : self.period.is_windowed_default() \ if w is not None else None , "wstart" : d2str(w.start_date()) if w is not None else None , "wend" : d2str(w.last_date()) if w is not None else None , "sponsored" : sponsored , "sponsor" : self.period.session.project.sponsor_text() , "receivers" : self.period.get_rcvrs_json() } # include the accounting but keep the dict flat if self.period.accounting is not None: accounting_js = self.period.accounting.jsondict() # make sure the final jsondict has only one 'id' accounting_id = accounting_js.pop('id') accounting_js.update({'accounting_id' : accounting_id}) js.update(accounting_js) return js
def clone(self, group = None): """ Creates a clone of the template, assigning the 'group' field to the parameter 'group' if this parameter is provided. The object will not be identical: there will be a different id, for instance. """ ma = Maintenance_Activity(); ma.save() # will be overwritten if template and group provided. ma.group = group if group else self.group # subject to a more recent one being found (see below) template = self # If this is a repeat template: if self.repeat_interval: ma.repeat_interval = 0 ma.repeat_end = None ma.repeat_template = self.repeat_template \ if self.repeat_template else self if group: template = self.get_template(group) # all maintenance activities are based on local time, # i.e. start 8:00 AM means that regardless of whether # DST is active or not. To do this, we get ET version # of group and template start so that it can be saved # as the appropriate UTC time to account for DST. t_start = template.get_start('ET') g_start = TimeAgent.utc2est(group.get_start()) start = datetime(g_start.year, g_start.month, g_start.day, t_start.hour, t_start.minute) # if this is a template, include the original creation # date for the repeat activity. ma.modifications.add(template.modifications.all()[0]) else: # we want the group's date, and the original's time in ET if group: start = datetime(group.get_start().date().year, group.get_start().date().month, group.get_start().date().day, self.get_start('ET').hour, self.get_start('ET').minute) else: start = self.get_start('ET') ma.copy_data(template) ma.set_start(start if start else template.start, 'ET' if start else None) ma.save() return ma
def GenerateBlackoutReport(): outfile = open("./DssBlackoutReport.txt", 'w') now = datetime.utcnow() later = now + timedelta(days = 7) outfile.write("Project | Start (UTC) | End (UTC) | Start (ET) | End (ET)\n") outfile.write("-------------------------------------------------------------------\n") sorted_projects = sorted(Project.objects.filter(complete = False) , lambda x, y: cmp(x.pcode, y.pcode)) for p in sorted_projects: blackouts = p.get_blackout_times(now, later) if blackouts: for start, end in blackouts: outfile.write("%s | %s | %s | %s | %s\n" % \ (p.pcode.ljust(11) , start.strftime("%m-%d %H:%M") , end.strftime("%m-%d %H:%M") , TimeAgent.utc2est(start).strftime("%m-%d %H:%M") , TimeAgent.utc2est(end).strftime("%m-%d %H:%M"))) outfile.close()
def getBlackedOutSchedulableTime(self, start, end): """ Of the hours in the given range that are schedulable, how many have been blacked out? Returns tuple of hours (scheduble but ignoring blackouts , scheduable but blacked out) Returns tuple of (scheduable but ignoring blackouts total , scheduable but blacked out total , [2-tuple of scheduable-but-ignoring-blackouts range)] , [[2-tuple of scheduable-but-blacked-out-range]]) """ nss1 = self.get_time_not_schedulable(start, end, blackouts=False) nss = self.trim_events(nss1, start, end) # now convert the non-schedulable time ranges to the # time that IS schedulable: schedulable = self.compliment_events(nss, start, end) # how much time is that? hrsSchedulable = sum([TimeAgent.timedelta2minutes(s[1] - s[0]) / 60.0 for s in schedulable]) # now, for each chunk of schedulable time, how much is # blacked out? hrsBlackedOut = 0.0 bss = [] # print "schedulable loop:" for s in schedulable: bs = self.project.get_blackout_times(s[0], s[1]) # but these blackout times might not match to the schedulable # end points, so we may need to truncate them bs = self.trim_events(bs, s[0], s[1]) if len(bs) != 0: bss.append(bs) bsTime = sum([TimeAgent.timedelta2minutes(b[1] - b[0]) / 60.0 for b in bs]) hrsBlackedOut += bsTime # return a summary of what we've found return (hrsSchedulable, hrsBlackedOut, schedulable, bss)
def get_monthly_due_dates(template): start_date = template._start.date() end = template.repeat_end dates = [] midnight = time(0, 0, 0) months = 0 ddate = start_date while ddate < end: dates.append(datetime.combine(ddate, midnight)) months = months + 1 ddate = TimeAgent.add_months(start_date, months) return dates
def _get_fixed_maint_events(mags, day, timezone): """ _get_fixed_maint_events(mags, day, timezone) Takes a set of maintenance activity groups and returns the one for 'day' if there is a fixed one for 'day'. """ evs = [] day = TimeAgent.truncateDt(day) tomorrow = day + timedelta(1) for mag in mags: if mag.period: # fixed if period is set if ( TimeAgent.truncateDt(mag.get_start(timezone)) == day or TimeAgent.truncateDt(mag.get_end(timezone)) == day ): ev = CalEventFixedMaintenance( mag, mag.get_start(timezone) < day, mag.get_end(timezone) >= tomorrow, True, timezone ) evs.append(ev) return evs
def read(self, request, *args, **kws): if len(args) == 1: tz, = args startPeriods = request.GET.get("startPeriods", datetime.now().strftime("%Y-%m-%d")) daysPeriods = request.GET.get("daysPeriods", '14') dt = TimeAgent.str2dt(startPeriods) start = dt if tz == 'UTC' else TimeAgent.est2utc(dt) duration = int(daysPeriods) * 24 * 60 periods = Period.get_periods(start, duration) pjson = [DssPeriodHttpAdapter(p).jsondict(tz) for p in periods] return HttpResponse( json.dumps(dict(total = len(periods) , periods = pjson , success = 'ok')) , content_type = "application/json") else: tz, id = args p = Period.objects.get(id = id) return HttpResponse( json.dumps(dict(DssPeriodHttpAdapter(p).jsondict(tz) , success = 'ok')) , content_type = "application/json")
def get_start(self, tzname = None): if self.group: date = self.group.get_start().date() if self._start.hour < self.group.get_start().hour and \ self._start.hour < self.group.get_end().hour: date = date + timedelta(1) start = datetime(date.year, date.month, date.day, self._start.hour, self._start.minute) else: start = self._start return TimeAgent.utc2est(start) if tzname == 'ET' else start
def good_fit(template, mag): # Checks to see if this template wouldn't work better # elsewhere. If so, returns False. If not, returns True. # first, take care of simple cases: repeat = 1, or no # published mags this week, the template is due, and this # is the highest U: if template.repeat_interval == 1: return False if better_fit(t, other_groups_today) else True if len(published_groups_this_week) == 0 \ and is_highest_U(mag): return True if is_P(self): dm = {-4: 40, -3: 30, -2: 20, -1: 10, 0: 0, 1: 15, 2: 25, 3: 35, 4: 45} today = TimeAgent.truncateDt(self.period.start) p = [mag.period for mag in published_groups_this_week] due_date = get_due_date(template) diff = (today - due_date).days if diff: # doesn't fall on this date. Is this the closest # period though? for j in p: if j != self.period: # check only other periods mod = (TimeAgent.truncateDt(j.start) - due_date).days # Test to see if it's a better fit in # another period. and if so, don't # use here. if dm[mod] < dm[diff]: return False return True return False
def receivers_schedule(request, *args, **kws): """ For a given period, specified by a start date and duration, show all the receiver changes. Receiver changes are aligned with maintenance days. """ # interpret the inputs startdate = request.GET.get("startdate", None) startdate = datetime.strptime(startdate, "%Y-%m-%d %H:%M:%S") if startdate else None duration = request.GET.get("duration", None) duration = int(duration) if duration else duration # use the input to get the basic rx schedule schedule = Receiver_Schedule.extract_schedule(startdate, duration) jsonschd = Receiver_Schedule.jsondict(schedule) # some clients also need the diff schedule diff = Receiver_Schedule.diff_schedule(schedule) jsondiff = Receiver_Schedule.jsondict_diff(diff).get("diff_schedule", None) # get the dates for maintenace that cover from the start of this # rcvr schedule. maintenance = [ TimeAgent.dt2str(p.start) for p in Period.objects.filter(session__observing_type__type="maintenance", start__gte=startdate).order_by( "start" ) ] # which receivers are temporarily unavailable? unavailable = [r.jsondict() for r in Receiver.objects.filter(available=False).order_by("freq_low")] # clients want to also know all the latest rcvrs rcvrs = [r.jsondict() for r in Receiver.objects.all().order_by("freq_low") if r.abbreviation != "NS"] return HttpResponse( json.dumps( { "schedule": jsonschd, "diff": jsondiff, "maintenance": maintenance, "unavailable": unavailable, "receivers": rcvrs, } ), mimetype="text/plain", )
def setUp(self): super(TestEmail, self).setUp() weekdays = {"0" : "Sun", "1" : "Mon", "2" : "Tue", "3" : "Wed", "4" : "Thu", "5" : "Fri", "6" : "Sat"} months = {"1" : "Jan", "2" : "Feb", "3" : "Mar", "4" : "Apr", "5" : "May", "6" : "Jun", "7" : "Jul", "8" : "Aug", "9" : "Sep", "10" : "Oct", "11" : "Nov", "12" : "Dec"} self.sender = "frog@pond" self.recipients_list = ["toad@toadstool", "newt@leaflitter"] self.recipients = "toad@toadstool,newt@leaflitter" self.subject = "Flies are yummy" self.body = "Delicious!" self.date = datetime.now() self.datestring = "%s, %s %s %s -0%d00" % (weekdays[self.date.strftime("%w")], self.date.strftime("%d"), months[str(int(self.date.strftime("%m")))], self.date.strftime("%Y %H:%M:%S"), TimeAgent.utcoffset()) self.text = 'From: %s\r\nTo: %s\r\nDate: %s\r\nSubject: %s\r\n\r\n%s\r\n' \ % (self.sender, self.recipients, self.datestring, self.subject, self.body) self.e1 = Email() self.e2 = Email(sender = self.sender, recipients = self.recipients, subject = self.subject, body = self.body, date = self.date)
def get_horizontal(self): "Returns the horizontal component in sexigesimal form." if self.horizontal is None: return "" if self.system.name == 'Galactic': return self.get_deg(self.horizontal) horz = TimeAgent.rad2hr(self.horizontal) mins = (horz - int(horz)) * 60 secs = (mins - int(mins)) * 60 if abs(secs - 60.) < 0.1: mins = int(mins) + 1 if abs(mins - 60.) < 0.1: mins = 0.0 horz = int(horz) + 1 secs = 0.0 return "%02i:%02i:%04.1f" % (int(horz), int(mins), secs)
def update_solar_avoid_obs_param(self, fdata, old_value): """ For taking a json dict and converting its given solar avoid float field into a 'Solar Avoid' float observing parameter. """ new_value = self.get_field(fdata, "solar_avoid", None, float) if new_value is not None: # make sure it's in a legal range try: fv = float(new_value) if fv < 0.0 or fv > 360.0: # should this be 90? return # value out of range new_value = TimeAgent.deg2rad(new_value) # DB in radians except: return # nonsense value parameter = Parameter.objects.filter(name="Solar Avoid")[0] self.update_parameter(old_value, new_value, parameter)
def _get_future_maintenance_dates3(): today = TimeAgent.truncateDt(datetime.now()) mp = Period.objects\ .filter(session__observing_type__type = "maintenance")\ .latest("start") last_date = mp.start week = today - timedelta(today.weekday()) # get the date of the Monday of this week. dates = {} # now loop, one week at a time, until that last date, gathering # all the maintenance periods. Each group will be represented as # a day of the week: 'A' = 0 (Monday), 'B' = 1 (Tuesday), etc. # These dates are then entered into the list of possible future # dates. while week < last_date: groups = Maintenance_Activity_Group.get_maintenance_activity_groups(week) for i in groups: d = str(i.get_start().date()) if not dates.has_key(d): dates[d] = [] # we want either the start time--in format "HH:MM"--if a # period is assigned, or the rank, if still floating. if (i.period): # t will be in format "HH:MM:SS" t = str(i.get_start(tzname = 'ET').time()) # use only "HH:MM" part of time string dates[d].append(t[0:t.rfind(":")]) else: dates[d].append(str(i.rank)) week += timedelta(7) for i in dates: dates[i].sort() return dates
def _get_incidental_events(today, timezone): """ Gathers up all the non-maintenance-period maintenance activities, and returns them in a list of CalEvent objects. """ utc_today = TimeAgent.est2utc(today) if timezone == "ET" else today mas = ( Maintenance_Activity.objects.filter(_start__gte=utc_today) .filter(_start__lt=utc_today + timedelta(days=1)) .filter(group=None) .filter(deleted=False) .order_by("_start") ) if mas.exists(): ev = CalEventIncidental(mas, TZ=timezone) return [ev] return []
def get_deg(self, value): degs = TimeAgent.rad2deg(value) if degs < 0: degs = abs(degs) sign = "-" else: sign = " " fpart, ddegs = modf(degs) fpart, dmins = modf(fpart * 60) dsecs = round(fpart * 60, 1) if dsecs > 59.9: dmins = dmins + 1 dsecs = 0.0 if dmins > 59.9: ddegs = ddegs + 1 dmins = 0.0 return "%s%02i:%02i:%04.1f" % (sign, int(ddegs), int(dmins), dsecs)
def getSchedulingRange(self, firstDay, timezone, days): """ Converts given time range (start dt, days) to 'scheduling range' (start dt, minutes) """ startHour = 0 if timezone == 'UTC' else self.getUTCHour(firstDay, 0) start = datetime(firstDay.year , firstDay.month , firstDay.day , startHour ) lastDay = firstDay + timedelta(days = days) end = datetime(lastDay.year , lastDay.month , lastDay.day , self.getUTCHour(lastDay, self.schedulingEnd) ) duration = TimeAgent.dtDiffMins(end, start) return (start, duration)
from users.models import Maintenance_Activity, Maintenance_Activity_Group from scheduler.models import Period from datetime import datetime, timedelta from nell.utilities import TimeAgent from scheduler.models import Period periods = Period.objects.filter(session__observing_type__type = "maintenance")\ .exclude(state__name = "Deleted").order_by("start") for p in periods: if p: mag = Maintenance_Activity_Group() mag.save() mag.period = p mag.week = TimeAgent.truncateDt(p.start - timedelta(p.start.weekday())) mag.deleted = True if p.state.name == 'Deleted' else False mag.save() print "Period %i -> Group %i" % (p.id, mag.id) mas = [m for m in p.maintenance_activity_set.all()] for ma in mas: ma.group = mag ma.period = None ma.save() mags = Maintenance_Activity_Group.objects.all() week = set() for mag in mags: week.add(mag.week)
def jsondict(self): irradiance = self.sesshun.irradiance() solarAvoid = self.sesshun.get_solar_avoidance() solarAvoid = None if solarAvoid is None else TimeAgent.rad2deg(solarAvoid) # DB in radians d = {"id" : self.sesshun.id , "pcode" : self.sesshun.project.pcode , "handle" : self.sesshun.toHandle() , "type" : self.sesshun.session_type.type , "science" : self.sesshun.observing_type.type , "total_time" : self.sesshun.allotment.total_time , "PSC_time" : self.sesshun.allotment.psc_time , "sem_time" : self.sesshun.allotment.max_semester_time , "remaining" : 0 if self.sesshun.observing_type.type == "maintenance" \ else TimeAccounting().getTimeRemaining(self.sesshun) , "grade" : self.sesshun.allotment.grade , "orig_ID" : self.sesshun.original_id , "name" : self.sesshun.name , "freq" : self.sesshun.frequency , "req_max" : self.sesshun.max_duration , "req_min" : self.sesshun.min_duration , "between" : self.sesshun.time_between , "enabled" : self.sesshun.status.enabled , "authorized" : self.sesshun.status.authorized , "complete" : self.sesshun.status.complete , "backup" : self.sesshun.status.backup , "guaranteed" : self.sesshun.guaranteed() , "gas" : self.sesshun.good_atmospheric_stability() or False , "transit" : self.sesshun.transit() or False #, "nighttime" : self.sesshun.nighttime() or False , "time_of_day": self.sesshun.get_time_of_day() , "lst_ex" : self.sesshun.get_lst_string('LST Exclude') or "" , "lst_in" : self.sesshun.get_lst_string('LST Include') or "" , "receiver" : self.sesshun.get_receiver_req() , "project_complete" : "Yes" if self.sesshun.project.complete else "No" , "xi_factor" : self.sesshun.get_min_eff_tsys_factor() or 1.0 , "el_limit" : self.sesshun.get_elevation_limit() or None # None is default , "solar_avoid": solarAvoid , "trk_err_threshold" : self.sesshun.get_tracking_error_threshold() , "src_size" : self.sesshun.get_source_size() , "keyhole" : self.sesshun.keyhole() , "irradiance" : 300 if irradiance is None and self.sesshun.observing_type.type == 'continuum' else irradiance } try: target = self.sesshun.target except Target.DoesNotExist: pass else: d.update({"source" : target.source , "coord_mode" : target.system.name , "source_h" : TimeAgent.rad2deg(target.horizontal) \ if target.system.name == 'Galactic' \ else TimeAgent.rad2hr(target.horizontal) , "source_v" : TimeAgent.rad2deg(target.vertical) }) # Remove all None values for k, v in d.items(): if v is None: _ = d.pop(k) return d