Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 37 additions & 31 deletions backend/scheduler/core/events/queue/nightchanges.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,35 +345,41 @@ def rnd_min(dt: datetime) -> datetime:
def to_json(self) -> dict:
utc = ZoneInfo('UTC')
return {
n_idx: {site.name: [{'startTimeSlot': te.start_time_slot,
'event': {'site': te.event.site.name,
'time': te.event.time.strftime(self._datetime_formatter),
'description': te.event.description,
},
'plan': {'start': te.plan_generated.start.astimezone(utc).strftime(self._datetime_formatter),
'end': te.plan_generated.end.astimezone(utc).strftime(self._datetime_formatter),
'site': te.plan_generated.site.name,
'visits': [{"starTime": v.start_time.astimezone(utc).strftime(self._datetime_formatter),
"endTime": (v.start_time+
v.time_slots*te.plan_generated.time_slot_length).strftime(self._datetime_formatter),
"obsId": v.observation.id.id,
"atomStartIdx": v.atom_start_idx,
"atomEndIdx": v.atom_end_idx,
"altitude": alt,
"instrument": inst.id if (inst := v.observation.instrument()) else '',
"obs_class": v.observation.obs_class.name,
"score": v.score,
"peakScore": v.peak_score,
"completion": v.completion}
for v, alt in zip(te.plan_generated.visits, te.plan_generated.alt_degs)],
'nightStats': {
'timeLoss': te.plan_generated.night_stats.time_loss,
'planScore': te.plan_generated.night_stats.plan_score,
'completionFraction': te.plan_generated.night_stats.completion_fraction,
'programCompletion': te.plan_generated.night_stats.program_completion
}
} if te.plan_generated else {}
} for te in time_entries]
for site, time_entries in by_site.items()
} for n_idx, by_site in self.timeline.items()
n_idx: {
site.name: [
{
'startTimeSlot': te.start_time_slot,
'event': {
'site': te.event.site.name,
'time': te.event.time.strftime(self._datetime_formatter),
'description': te.event.description,
},
'plan': {
'start': te.plan_generated.start.astimezone(utc).strftime(self._datetime_formatter),
'end': te.plan_generated.end.astimezone(utc).strftime(self._datetime_formatter),
'site': te.plan_generated.site.name,
'visits': [
{
"starTime": v.start_time.astimezone(utc).strftime(self._datetime_formatter),
"endTime": (v.start_time + v.time_slots*te.plan_generated.time_slot_length).strftime(self._datetime_formatter),
"obsId": v.observation.id.id,
"atomStartIdx": v.atom_start_idx,
"atomEndIdx": v.atom_end_idx,
"altitude": alt,
"instrument": inst.id if (inst := v.observation.instrument()) else '',
"obs_class": v.observation.obs_class.name,
"score": v.score,
"peakScore": v.peak_score,
"completion": v.completion
} for v, alt in zip(te.plan_generated.visits, te.plan_generated.alt_degs)],
'nightStats': {
'timeLoss': te.plan_generated.night_stats.time_loss,
'planScore': te.plan_generated.night_stats.plan_score,
'completionFraction': te.plan_generated.night_stats.completion_fraction,
'programCompletion': te.plan_generated.night_stats.program_completion
}
} if te.plan_generated else {}
} for te in time_entries
] for site, time_entries in by_site.items()
} for n_idx, by_site in self.timeline.items()
}
25 changes: 22 additions & 3 deletions backend/scheduler/core/statscalculator/statscalculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,14 @@ def calculate_stitched_timeline_stats(timeline: NightlyTimeline,

if entry_idx == len(timeline.stitched_timeline[night_idx][site]) - 1:
for v in plan.visits:
obs = collector.get_observation(v.observation.id)
# The visit keeps its own Observation: in RT the collector is rebuilt on every
# event, so observations from earlier plans might not be in it anymore.
obs = v.observation
program = collector.get_program(obs.belongs_to)
if program is None:
logger.warning(f'Program {obs.belongs_to.id} for observation {obs.id.id} '
f'not found in the collector, skipping it in the run summary.')
continue

# Check if program is on the table
metrics_per_program.setdefault(program.id, 0.0)
Expand All @@ -76,8 +82,14 @@ def calculate_stitched_timeline_stats(timeline: NightlyTimeline,
plan_conditions = []
completion_fraction: Counter[Band] = Counter({b: 0 for b in Band})

# Altitudes are only calculated once per plan: previous entries are revisited on every
# RT event, and their target info might not be in the current collector anymore.
calculate_alt_degs = len(plan.alt_degs) != len(plan.visits)
if calculate_alt_degs:
plan.alt_degs = []

for visit in plan.visits:
obs = collector.get_observation(visit.observation.id)
obs = visit.observation
# check if obs is a too
if obs.too_type is not None:
n_toos += 1
Expand All @@ -92,7 +104,14 @@ def calculate_stitched_timeline_stats(timeline: NightlyTimeline,
completion_fraction[obs.band] += 1

# Calculate altitude data
ti = collector.get_target_info(visit.observation.id)
if not calculate_alt_degs:
continue
ti = collector.get_target_info(obs.id)
if ti is None or night_idx not in ti:
logger.warning(f'No target info for observation {obs.id.id} on night {night_idx}.')
# Keep alt_degs aligned with the visits, as they are zipped together.
plan.alt_degs.append([])
continue
end_time_slot = visit.start_time_slot + visit.time_slots
values = ti[night_idx].alt[visit.start_time_slot: end_time_slot]
alt_degs = [val.dms[0] + (val.dms[1] / 60) + (val.dms[2] / 3600) for val in values]
Expand Down
1 change: 1 addition & 0 deletions changelog.d/GSCHED-1030.fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Retrieve the observation data from the visits when calculating the timeline stats, collector may have None type observations
Loading