Overnight bug fixes

This commit is contained in:
Ian Renton
2026-08-18 17:56:31 +01:00
parent 38bc03a603
commit 7f1a5ae3be
12 changed files with 152 additions and 130 deletions
+54 -47
View File
@@ -1,6 +1,7 @@
import logging import logging
from datetime import datetime from datetime import datetime
from typing import cast from typing import cast
from xml.parsers.expat import ExpatError
import pytz import pytz
from rss_parser import Parser as RSSParser from rss_parser import Parser as RSSParser
@@ -25,56 +26,62 @@ class WOTA(HTTPAlertProvider):
def _http_response_to_alerts(self, http_response): def _http_response_to_alerts(self, http_response):
new_alerts = [] new_alerts = []
rss = cast(RSS, RSSParser.parse(http_response.content.decode("utf-8-sig")))
# Iterate through source data
for source_alert in rss.channel.items:
# Reject GUID missing or zero
if (
not source_alert.guid
or not source_alert.guid.content
or source_alert.guid.content == "http://www.wota.org.uk/alerts/0"
):
continue
# Pick apart the title try:
dx_call = None rss = cast(RSS, RSSParser.parse(http_response.content.decode("utf-8-sig")))
ref = None # Iterate through source data
ref_name = None for source_alert in rss.channel.items:
try: # Reject GUID missing or zero
title_split = source_alert.title.split(" on ") if (
dx_call = title_split[0] not source_alert.guid
if len(title_split) > 1: or not source_alert.guid.content
ref_split = title_split[1].split(" - ") or source_alert.guid.content == "http://www.wota.org.uk/alerts/0"
ref = str(ref_split[0]) ):
if len(ref_split) > 1: continue
ref_name = str(ref_split[1])
except Exception:
logger.warning(f"Could not parse WOTA alert title: {source_alert.description}")
# Pick apart the description # Pick apart the title
comment = None dx_call = None
freqs_modes = None ref = None
try: ref_name = None
desc_split = source_alert.description.split(". ") try:
freqs_modes = desc_split[0].replace("Frequencies/modes:", "").strip() title_split = source_alert.title.split(" on ")
if len(desc_split) > 1: dx_call = title_split[0]
comment = desc_split[1].strip() if len(title_split) > 1:
except Exception: ref_split = title_split[1].split(" - ")
logger.warning(f"Could not parse WOTA alert description: {source_alert.description}") ref = str(ref_split[0])
if len(ref_split) > 1:
ref_name = str(ref_split[1])
except Exception:
logger.warning(f"Could not parse WOTA alert title: {source_alert.description}")
time = datetime.strptime(source_alert.pub_date.content, self.RSS_DATE_TIME_FORMAT).astimezone(pytz.UTC) # Pick apart the description
comment = None
freqs_modes = None
try:
desc_split = source_alert.description.split(". ")
freqs_modes = desc_split[0].replace("Frequencies/modes:", "").strip()
if len(desc_split) > 1:
comment = desc_split[1].strip()
except Exception:
logger.warning(f"Could not parse WOTA alert description: {source_alert.description}")
# Convert to our alert format time = datetime.strptime(source_alert.pub_date.content, self.RSS_DATE_TIME_FORMAT).astimezone(pytz.UTC)
alert = Alert(
source=self.name, # Convert to our alert format
source_id=source_alert.guid.content, alert = Alert(
dx_calls=[dx_call], source=self.name,
freqs_modes=freqs_modes, source_id=source_alert.guid.content,
comment=comment, dx_calls=[dx_call],
sig_refs=[SIGRef(id=ref, sig="WOTA", name=ref_name)] if ref else [], freqs_modes=freqs_modes,
start_time=time.timestamp(), comment=comment,
) sig_refs=[SIGRef(id=ref, sig="WOTA", name=ref_name)] if ref else [],
start_time=time.timestamp(),
)
# Add to our list.
new_alerts.append(alert)
except ExpatError:
logger.warning("WOTA alert RSS feed was fetched but was invalid")
# Add to our list.
new_alerts.append(alert)
return new_alerts return new_alerts
+4 -4
View File
@@ -13,10 +13,10 @@ from providers.solarconditions.solar_conditions_provider import SolarConditionsP
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Each station gets polled roughly once every hour (3600 seconds). Note that to avoid a burst of requests to the server # Each station gets polled roughly once every two hours (7200 seconds). Note that to avoid a burst of requests to the
# every hour, the requests for data from each station are spaced out throughout the hour, leading to one request being # server every two hours, the requests for data from each station are spaced out throughout the period, leading to one
# sent every 1-2 minutes. # request being sent every 3-4 minutes.
POLL_INTERVAL = 3600 POLL_INTERVAL = 7200
# To avoid looking up all stations in the GIRO system and working out which ones are providing live data, this has been # To avoid looking up all stations in the GIRO system and working out which ones are providing live data, this has been
# manually determined and a CSV provided of all the stations that we can query for live data. # manually determined and a CSV provided of all the stations that we can query for live data.
STATIONS_INDEX = "datafiles/didbase-stations.csv" STATIONS_INDEX = "datafiles/didbase-stations.csv"
+10 -1
View File
@@ -1,3 +1,4 @@
import logging
from datetime import datetime from datetime import datetime
import requests import requests
@@ -7,6 +8,8 @@ from data.sig_ref import SIGRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
logger = logging.getLogger(__name__)
class Tiles(HTTPSpotProvider): class Tiles(HTTPSpotProvider):
"""Spot provider for Tiles on the Air""" """Spot provider for Tiles on the Air"""
@@ -39,13 +42,19 @@ class Tiles(HTTPSpotProvider):
# Iterate through source data # Iterate through source data
for source_spot in http_response.json()["spots"]: for source_spot in http_response.json()["spots"]:
# Convert to our spot format # Convert to our spot format
freq = None
try:
freq = float(strip_extra_decimal_points(source_spot["frequency"])) * 1000000
except ValueError:
logger.debug(f"Malformed frequency received from Tiles API: {source_spot['frequency']}")
spot = Spot( spot = Spot(
source=self.name, source=self.name,
source_id=source_spot["id"], source_id=source_spot["id"],
dx_call=source_spot["call_sign"].upper(), dx_call=source_spot["call_sign"].upper(),
# No separate spotter callsign, assume all spots are self-spots # No separate spotter callsign, assume all spots are self-spots
de_call=source_spot["call_sign"].upper(), de_call=source_spot["call_sign"].upper(),
freq=float(strip_extra_decimal_points(source_spot["frequency"])) * 1000000, freq=freq,
mode=source_spot["mode"].upper(), mode=source_spot["mode"].upper(),
comment=source_spot["notes"], comment=source_spot["notes"],
sig="Tiles", sig="Tiles",
+69 -63
View File
@@ -2,6 +2,7 @@ import logging
import re import re
from datetime import datetime from datetime import datetime
from typing import cast from typing import cast
from xml.parsers.expat import ExpatError
import pytz import pytz
from rss_parser import Parser from rss_parser import Parser
@@ -26,73 +27,78 @@ class WOTA(HTTPSpotProvider):
def _http_response_to_spots(self, http_response): def _http_response_to_spots(self, http_response):
new_spots = [] new_spots = []
rss = cast(RSS, Parser.parse(http_response.content.decode("utf-8-sig"))) try:
# Iterate through source data rss = cast(RSS, Parser.parse(http_response.content.decode("utf-8-sig")))
for source_spot in rss.channel.items: # Iterate through source data
try: for source_spot in rss.channel.items:
# Reject GUID missing or zero
if (
not source_spot.guid
or not source_spot.guid.content
or source_spot.guid.content == "http://www.wota.org.uk/spots/0"
):
continue
# Pick apart the title
dx_call = None
ref = None
ref_name = None
try: try:
title_split = source_spot.title.split(" on ") # Reject GUID missing or zero
dx_call = title_split[0] if (
if len(title_split) > 1: not source_spot.guid
ref_split = title_split[1].split(" - ") or not source_spot.guid.content
ref = str(ref_split[0]) or source_spot.guid.content == "http://www.wota.org.uk/spots/0"
if len(ref_split) > 1: ):
ref_name = str(ref_split[1]) continue
# Pick apart the title
dx_call = None
ref = None
ref_name = None
try:
title_split = source_spot.title.split(" on ")
dx_call = title_split[0]
if len(title_split) > 1:
ref_split = title_split[1].split(" - ")
ref = str(ref_split[0])
if len(ref_split) > 1:
ref_name = str(ref_split[1])
except Exception:
logger.warning(f"Could not parse WOTA spot title: {source_spot.title}")
# Pick apart the description
freq_hz = None
mode = None
comment = None
spotter = None
try:
desc_split = source_spot.description.split(". ")
freq_mode = desc_split[0].replace("Frequencies/modes:", "").strip()
if freq_mode and freq_mode != "-":
freq_mode_split = re.split(r"[\-\s]+", freq_mode)
freq_hz = float(freq_mode_split[0].replace("'", ".")) * 1000000
if len(freq_mode_split) > 1:
mode = freq_mode_split[1].upper()
if len(desc_split) > 1:
comment = desc_split[1].strip()
if len(desc_split) > 2:
spotter = desc_split[2].replace("Spotted by ", "").replace(".", "").upper().strip()
except Exception:
logger.warning(f"Could not parse WOTA spot description: {source_spot.description}")
time = datetime.strptime(source_spot.pub_date.content, self.RSS_DATE_TIME_FORMAT).astimezone(pytz.UTC)
# Convert to our spot format
spot = Spot(
source=self.name,
source_id=source_spot.guid.content,
dx_call=dx_call,
de_call=spotter,
freq=freq_hz,
mode=mode,
comment=comment,
sig="WOTA",
sig_refs=[SIGRef(id=ref, sig="WOTA", name=ref_name)] if ref else [],
time=time.timestamp(),
)
new_spots.append(spot)
except Exception: except Exception:
logger.warning(f"Could not parse WOTA spot title: {source_spot.title}") logger.exception("Exception parsing WOTA spot")
# Pick apart the description except ExpatError:
freq_hz = None logger.warning("WOTA spot RSS feed was fetched but was invalid")
mode = None
comment = None
spotter = None
try:
desc_split = source_spot.description.split(". ")
freq_mode = desc_split[0].replace("Frequencies/modes:", "").strip()
if freq_mode and freq_mode != "-":
freq_mode_split = re.split(r"[\-\s]+", freq_mode)
freq_hz = float(freq_mode_split[0].replace("'", ".")) * 1000000
if len(freq_mode_split) > 1:
mode = freq_mode_split[1].upper()
if len(desc_split) > 1:
comment = desc_split[1].strip()
if len(desc_split) > 2:
spotter = desc_split[2].replace("Spotted by ", "").replace(".", "").upper().strip()
except Exception:
logger.warning(f"Could not parse WOTA spot description: {source_spot.description}")
time = datetime.strptime(source_spot.pub_date.content, self.RSS_DATE_TIME_FORMAT).astimezone(pytz.UTC)
# Convert to our spot format
spot = Spot(
source=self.name,
source_id=source_spot.guid.content,
dx_call=dx_call,
de_call=spotter,
freq=freq_hz,
mode=mode,
comment=comment,
sig="WOTA",
sig_refs=[SIGRef(id=ref, sig="WOTA", name=ref_name)] if ref else [],
time=time.timestamp(),
)
new_spots.append(spot)
except Exception:
logger.exception("Exception parsing WOTA spot")
return new_spots return new_spots
def can_submit_spot(self, sig): def can_submit_spot(self, sig):
+1 -1
View File
@@ -76,7 +76,7 @@
</div> </div>
<script src="/static/js/add-spot.js?v=1786980624"></script> <script src="/static/js/add-spot.js?v=1787072191"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-add-spot").addClass("active"); $("#nav-link-add-spot").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+1 -1
View File
@@ -84,7 +84,7 @@
</div> </div>
<script src="/static/js/alerts.js?v=1786980625"></script> <script src="/static/js/alerts.js?v=1787072191"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-alerts").addClass("active"); $("#nav-link-alerts").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+2 -2
View File
@@ -76,8 +76,8 @@
</div> </div>
<script src="/static/js/spotsbandsandmap.js?v=1786980624"></script> <script src="/static/js/spotsbandsandmap.js?v=1787072191"></script>
<script src="/static/js/bands.js?v=1786980624"></script> <script src="/static/js/bands.js?v=1787072191"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-bands").addClass("active"); $("#nav-link-bands").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+5 -5
View File
@@ -1,6 +1,6 @@
{% extends "skeleton.html" %} {% extends "skeleton.html" %}
{% block head_extra %} {% block head_extra %}
<link rel="stylesheet" href="/static/css/style.css?v=1786980624" type="text/css"> <link rel="stylesheet" href="/static/css/style.css?v=1787072191" type="text/css">
<link href="/static/vendor/css/bootstrap-5.3.8.min.css" rel="stylesheet"> <link href="/static/vendor/css/bootstrap-5.3.8.min.css" rel="stylesheet">
<link href="/static/vendor/css/fontawesome-6.7.2.min.css" rel="stylesheet"> <link href="/static/vendor/css/fontawesome-6.7.2.min.css" rel="stylesheet">
<link href="/static/vendor/css/solid-6.7.2.min.css" rel="stylesheet"> <link href="/static/vendor/css/solid-6.7.2.min.css" rel="stylesheet">
@@ -15,10 +15,10 @@
window.fetchEventSource = fetchEventSource; window.fetchEventSource = fetchEventSource;
</script> </script>
<script src="/static/js/utils.js?v=1786980624"></script> <script src="/static/js/utils.js?v=1787072191"></script>
<script src="/static/js/ui-ham.js?v=1786980624"></script> <script src="/static/js/ui-ham.js?v=1787072191"></script>
<script src="/static/js/geo.js?v=1786980624"></script> <script src="/static/js/geo.js?v=1787072191"></script>
<script src="/static/js/common.js?v=1786980624"></script> <script src="/static/js/common.js?v=1787072191"></script>
{% end %} {% end %}
{% block body %} {% block body %}
<div class="container"> <div class="container">
+1 -1
View File
@@ -284,7 +284,7 @@
</div> </div>
<script src="/static/vendor/js/chart-4.4.9.umd.min.js"></script> <script src="/static/vendor/js/chart-4.4.9.umd.min.js"></script>
<script src="/static/js/conditions.js?v=1786980624"></script> <script src="/static/js/conditions.js?v=1787072191"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-conditions").addClass("active"); $("#nav-link-conditions").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+2 -2
View File
@@ -109,8 +109,8 @@
<script src="/static/vendor/js/leaflet-cqzones.js"></script> <script src="/static/vendor/js/leaflet-cqzones.js"></script>
<script src="/static/vendor/js/leaflet-workedallbritainireland.js" type="module"></script> <script src="/static/vendor/js/leaflet-workedallbritainireland.js" type="module"></script>
<script src="/static/js/spotsbandsandmap.js?v=1786980625"></script> <script src="/static/js/spotsbandsandmap.js?v=1787072191"></script>
<script src="/static/js/map.js?v=1786980625"></script> <script src="/static/js/map.js?v=1787072191"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-map").addClass("active"); $("#nav-link-map").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+2 -2
View File
@@ -113,8 +113,8 @@
</div> </div>
<script src="/static/js/spotsbandsandmap.js?v=1786980624"></script> <script src="/static/js/spotsbandsandmap.js?v=1787072191"></script>
<script src="/static/js/spots.js?v=1786980624"></script> <script src="/static/js/spots.js?v=1787072191"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-spots").addClass("active"); $("#nav-link-spots").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+1 -1
View File
@@ -86,7 +86,7 @@
</div> </div>
</div> </div>
<script src="/static/js/status.js?v=1786980624"></script> <script src="/static/js/status.js?v=1787072191"></script>
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$("#nav-link-status").addClass("active"); $("#nav-link-status").addClass("active");