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: for alert in alerts:
# Fill in any blanks # Fill in any blanks
alert.infer_missing() 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 # Add to the list, provided it heas not already expired.
# future, or if not, then the start date must be at least in the last 24 hours. if not alert.expired():
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()):
self.alerts.add(alert.id, alert, expire=MAX_ALERT_AGE) self.alerts.add(alert.id, alert, expire=MAX_ALERT_AGE)
# Stop any threads and prepare for application shutdown # Stop any threads and prepare for application shutdown

View File

@@ -34,13 +34,10 @@ class CleanupTimer:
self.alerts.expire() self.alerts.expire()
# Alerts can persist in the system for a while, so we want to explicitly clean up any alerts that have # 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 # expired
# ago.
for id in list(self.alerts.iterkeys()): for id in list(self.alerts.iterkeys()):
alert = self.alerts[id] alert = self.alerts[id]
if (alert.end_time and alert.end_time < datetime.now(pytz.UTC).timestamp()) or ( if alert.expired():
not alert.end_time and alert.start_time < (datetime.now(
pytz.UTC) - timedelta(days=1)).timestamp()):
self.alerts.evict(id) self.alerts.evict(id)
self.status = "OK" self.status = "OK"

View File

@@ -1,9 +1,9 @@
import copy
import hashlib import hashlib
import json import json
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime, timedelta
import copy
import pytz import pytz
from core.constants import DXCC_FLAGS from core.constants import DXCC_FLAGS
@@ -117,4 +117,12 @@ class Alert:
# JSON serialise # JSON serialise
def to_json(self): 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 = [] alerts = []
for k in alert_ids: for k in alert_ids:
alerts.append(self.alerts.get(k)) 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)) alerts = sorted(alerts, key=lambda alert: (alert.start_time if alert and alert.start_time else 0))
for k in query.keys(): for k in query.keys():
match k: match k:
@@ -195,8 +197,10 @@ class WebServer:
alerts = [a for a in alerts if a.received_time > since] alerts = [a for a in alerts if a.received_time > since]
case "max_duration": case "max_duration":
max_duration = int(query.get(k)) 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 ( # Check the duration if end_time is provided. If end_time is not provided, assume the activation is
not a.end_time and datetime.now(pytz.UTC).timestamp() - a.start_time <= max_duration)] # "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": case "source":
sources = query.get(k).split(",") sources = query.get(k).split(",")
alerts = [a for a in alerts if a.source in sources] 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"]], sig_refs_names=[source_spot["NAME"]],
time=datetime.strptime(source_spot["DATE"] + source_spot["TIME"], "%Y%m%d%H%M").replace( time=datetime.strptime(source_spot["DATE"] + source_spot["TIME"], "%Y%m%d%H%M").replace(
tzinfo=pytz.UTC).timestamp(), tzinfo=pytz.UTC).timestamp(),
latitude=float(source_spot["LAT"]) if (source_spot["LAT"] != "") else None, latitude=float(source_spot["LAT"]) if (source_spot["LAT"] and source_spot["LAT"] != "") else None,
# Seen GMA spots with no lat/lon # Seen GMA spots with no (or empty) lat/lon
longitude=float(source_spot["LON"]) if (source_spot["LON"] != "") else None) 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. # 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"], 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" // 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 // 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. // last hour.
onNow = alerts.filter(a => (a["end_time"] != null && moment.unix(a["end_time"]).utc().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 && moment.unix(a["start_time"]).utc().add(1, 'hours').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()); 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()); 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) { if (onNow.length > 0) {
table.find('tbody').append('<tr class="table-primary"><td colspan="100" style="text-align:center;">On Now</td></tr>'); table.find('tbody').append('<tr class="table-primary"><td colspan="100" style="text-align:center;">On Now</td></tr>');
addAlertRowsToTable(table.find('tbody'), onNow); addAlertRowsToTable(table.find('tbody'), onNow);

View File

@@ -66,13 +66,13 @@ function updateRefreshDisplay() {
count = REFRESH_INTERVAL_SEC - secSinceUpdate; count = REFRESH_INTERVAL_SEC - secSinceUpdate;
if (count <= 60) { if (count <= 60) {
var number = count.toFixed(0); 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 { } else {
var number = Math.round(count / 60.0).toFixed(0); 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);
} }
} }