Fix some bugginess with how expired alerts and alert max_duration were handled. Closes #34

This commit is contained in:
Ian Renton
2025-10-08 16:09:39 +01:00
parent d7f9c36b28
commit e01b6d5ea9
7 changed files with 35 additions and 23 deletions

View File

@@ -35,11 +35,8 @@ class AlertProvider:
for alert in alerts:
# Fill in any blanks
alert.infer_missing()
# Add to the list, provided it meets the semsible date test. If alerts have an end time, it must be in the
# future, or if not, then the start date must be at least in the last 24 hours.
if (alert.end_time and alert.end_time > datetime.now(pytz.UTC).timestamp()) or (
not alert.end_time and alert.start_time > (datetime.now(
pytz.UTC) - timedelta(days=1)).timestamp()):
# Add to the list, provided it heas not already expired.
if not alert.expired():
self.alerts.add(alert.id, alert, expire=MAX_ALERT_AGE)
# Stop any threads and prepare for application shutdown

View File

@@ -34,13 +34,10 @@ class CleanupTimer:
self.alerts.expire()
# Alerts can persist in the system for a while, so we want to explicitly clean up any alerts that have
# definitively ended, or if they have no definite end time, then if the start time was more than 24 hours
# ago.
# expired
for id in list(self.alerts.iterkeys()):
alert = self.alerts[id]
if (alert.end_time and alert.end_time < datetime.now(pytz.UTC).timestamp()) or (
not alert.end_time and alert.start_time < (datetime.now(
pytz.UTC) - timedelta(days=1)).timestamp()):
if alert.expired():
self.alerts.evict(id)
self.status = "OK"

View File

@@ -1,9 +1,9 @@
import copy
import hashlib
import json
from dataclasses import dataclass
from datetime import datetime
from datetime import datetime, timedelta
import copy
import pytz
from core.constants import DXCC_FLAGS
@@ -117,4 +117,12 @@ class Alert:
# JSON serialise
def to_json(self):
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True)
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True)
# Decide if this alert has expired (in which case it should not be added to the system in the first place, and not
# returned by the web server if later requested, and removed by the cleanup functions. "Expired" is defined as
# either having an end_time in the past, or if it only has a start_time, then that start time was more than 3 hours
# ago. If it somehow doesn't have a start_time either, it is considered to be expired.
def expired(self):
return not self.start_time or (self.end_time and self.end_time < datetime.now(pytz.UTC).timestamp()) or (
not self.end_time and self.start_time < (datetime.now(pytz.UTC) - timedelta(hours=3)).timestamp())

View File

@@ -187,6 +187,8 @@ class WebServer:
alerts = []
for k in alert_ids:
alerts.append(self.alerts.get(k))
# We never want alerts that seem to be in the past
alerts = list(filter(lambda alert: not alert.expired(), alerts))
alerts = sorted(alerts, key=lambda alert: (alert.start_time if alert and alert.start_time else 0))
for k in query.keys():
match k:
@@ -195,8 +197,10 @@ class WebServer:
alerts = [a for a in alerts if a.received_time > since]
case "max_duration":
max_duration = int(query.get(k))
alerts = [a for a in alerts if (a.end_time and a.end_time - a.start_time <= max_duration) or (
not a.end_time and datetime.now(pytz.UTC).timestamp() - a.start_time <= max_duration)]
# Check the duration if end_time is provided. If end_time is not provided, assume the activation is
# "short", i.e. it always passes this check.
alerts = [a for a in alerts if (a.end_time and a.end_time - a.start_time <= max_duration) or
not a.end_time]
case "source":
sources = query.get(k).split(",")
alerts = [a for a in alerts if a.source in sources]

View File

@@ -37,9 +37,9 @@ class GMA(HTTPSpotProvider):
sig_refs_names=[source_spot["NAME"]],
time=datetime.strptime(source_spot["DATE"] + source_spot["TIME"], "%Y%m%d%H%M").replace(
tzinfo=pytz.UTC).timestamp(),
latitude=float(source_spot["LAT"]) if (source_spot["LAT"] != "") else None,
# Seen GMA spots with no lat/lon
longitude=float(source_spot["LON"]) if (source_spot["LON"] != "") else None)
latitude=float(source_spot["LAT"]) if (source_spot["LAT"] and source_spot["LAT"] != "") else None,
# Seen GMA spots with no (or empty) lat/lon
longitude=float(source_spot["LON"]) if (source_spot["LON"] and source_spot["LON"] != "") else None)
# GMA doesn't give what programme (SIG) the reference is for until we separately look it up.
ref_response = self.REF_INFO_CACHE.get(self.REF_INFO_URL_ROOT + source_spot["REF"],

View File

@@ -48,11 +48,17 @@ function updateTable() {
// Split alerts into three types, each of which will get its own table header: On now, next 24h, and later. "On now"
// is considered to be events with an end_time where start<now<end, or events with no end time that started in the
// last hour.
onNow = alerts.filter(a => (a["end_time"] != null && moment.unix(a["end_time"]).utc().isSameOrAfter() && moment.unix(a["start_time"]).utc().isBefore())
|| (a["end_time"] == null && moment.unix(a["start_time"]).utc().add(1, 'hours').isSameOrAfter() && moment.unix(a["start_time"]).utc().isBefore()));
onNow = alerts.filter(a => (a["end_time"] != null && a["end_time"] != 0 && moment.unix(a["end_time"]).utc().isSameOrAfter() && moment.unix(a["start_time"]).utc().isBefore())
|| ((a["end_time"] == null || a["end_time"] == 0) && moment.unix(a["start_time"]).utc().add(1, 'hours').isSameOrAfter() && moment.unix(a["start_time"]).utc().isBefore()));
next24h = alerts.filter(a => moment.unix(a["start_time"]).utc().isSameOrAfter() && moment.unix(a["start_time"]).utc().subtract(24, 'hours').isBefore());
later = alerts.filter(a => moment.unix(a["start_time"]).utc().subtract(24, 'hours').isSameOrAfter());
alerts.forEach(a => {
if (!(a in onNow) && !(a in next24h) && !(a in later)) {
console.log(moment.unix(a["start_time"]).format('YYYY-MM-DD hh:mm') + " " + moment.unix(a["end_time"]).format('YYYY-MM-DD hh:mm'));
}
});
if (onNow.length > 0) {
table.find('tbody').append('<tr class="table-primary"><td colspan="100" style="text-align:center;">On Now</td></tr>');
addAlertRowsToTable(table.find('tbody'), onNow);

View File

@@ -66,13 +66,13 @@ function updateRefreshDisplay() {
count = REFRESH_INTERVAL_SEC - secSinceUpdate;
if (count <= 60) {
var number = count.toFixed(0);
updatingString = "Updating in " + number + " second" + (number != "1" ? "s" : "") + ".&nbsp;&nbsp;";
updatingString = "Updating in <span class='nowrap'>" + number + " second" + (number != "1" ? "s" : "") + ".</span>";
} else {
var number = Math.round(count / 60.0).toFixed(0);
updatingString = "Updating in " + number + " minute" + (number != "1" ? "s" : "") + ".&nbsp;&nbsp;";
updatingString = "Updating in <span class='nowrap'>" + number + " minute" + (number != "1" ? "s" : "") + ".</span>";
}
}
$("#timing-container").text("Last updated at " + lastUpdateTime.format('HH:mm') + " UTC. " + updatingString);
$("#timing-container").html("Last updated at " + lastUpdateTime.format('HH:mm') + " UTC. " + updatingString);
}
}