mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-20 14:27:42 +00:00
Use ruff linter to fix issues and provide consistent formatting
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import csv
|
||||
import logging
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from threading import Thread, Event
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from threading import Event, Thread
|
||||
|
||||
import pytz
|
||||
import requests
|
||||
from requests.exceptions import ConnectionError, ReadTimeout, ConnectTimeout
|
||||
from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout
|
||||
|
||||
from core.constants import HTTP_HEADERS
|
||||
from providers.solarconditions.ionosonde_utils import compute_band_states
|
||||
@@ -40,9 +40,16 @@ class GIROIonosonde(SolarConditionsProvider):
|
||||
# entries so KC2G cache data is preserved.
|
||||
existing = self._solar_conditions.ionosonde_data or {}
|
||||
new_entries = {
|
||||
s["ursi"]: {"ursi": s["ursi"], "name": s["name"], "fof2": None, "muf": None,
|
||||
"luf": None, "band_states": None}
|
||||
for s in self._stations if s["ursi"] not in existing
|
||||
s["ursi"]: {
|
||||
"ursi": s["ursi"],
|
||||
"name": s["name"],
|
||||
"fof2": None,
|
||||
"muf": None,
|
||||
"luf": None,
|
||||
"band_states": None,
|
||||
}
|
||||
for s in self._stations
|
||||
if s["ursi"] not in existing
|
||||
}
|
||||
if new_entries:
|
||||
self.update_data({"ionosonde_data": {**existing, **new_entries}})
|
||||
@@ -50,7 +57,7 @@ class GIROIonosonde(SolarConditionsProvider):
|
||||
@staticmethod
|
||||
def _load_stations():
|
||||
stations = []
|
||||
with open(STATIONS_INDEX, newline='') as f:
|
||||
with open(STATIONS_INDEX, newline="") as f:
|
||||
for row in csv.reader(f):
|
||||
if len(row) >= 2:
|
||||
stations.append({"ursi": row[0].strip(), "name": row[1].strip()})
|
||||
@@ -94,8 +101,14 @@ class GIROIonosonde(SolarConditionsProvider):
|
||||
|
||||
# Merge GIRO's readings into any existing data for this station.
|
||||
existing = ionosonde_data.get(ursi, {})
|
||||
merged_fof2 = {**{float(t): v for t, v in (existing.get("fof2") or {}).items()}, **fof2}
|
||||
merged_muf = {**{float(t): v for t, v in (existing.get("muf") or {}).items()}, **muf}
|
||||
merged_fof2 = {
|
||||
**{float(t): v for t, v in (existing.get("fof2") or {}).items()},
|
||||
**fof2,
|
||||
}
|
||||
merged_muf = {
|
||||
**{float(t): v for t, v in (existing.get("muf") or {}).items()},
|
||||
**muf,
|
||||
}
|
||||
merged_luf = dict(luf) if luf else {}
|
||||
|
||||
merged_fof2 = {t: v for t, v in merged_fof2.items() if t >= cutoff_ts}
|
||||
@@ -104,7 +117,8 @@ class GIROIonosonde(SolarConditionsProvider):
|
||||
|
||||
band_states = compute_band_states(merged_fof2, merged_muf, merged_luf)
|
||||
ionosonde_data[ursi] = {
|
||||
"ursi": ursi, "name": name,
|
||||
"ursi": ursi,
|
||||
"name": name,
|
||||
"fof2": merged_fof2 or None,
|
||||
"muf": merged_muf or None,
|
||||
"luf": merged_luf or None,
|
||||
@@ -132,7 +146,7 @@ class GIROIonosonde(SolarConditionsProvider):
|
||||
return None, None, None
|
||||
return self._parse_all(http_response.text)
|
||||
except (ConnectTimeout, ReadTimeout):
|
||||
logging.warning(f"Timeout when accessing Giro ionosonde API.")
|
||||
logging.warning("Timeout when accessing Giro ionosonde API.")
|
||||
return None, None, None
|
||||
except ConnectionError:
|
||||
logging.warning("Connection error when accessing Giro ionosonde API.")
|
||||
@@ -147,14 +161,14 @@ class GIROIonosonde(SolarConditionsProvider):
|
||||
luf_data = {}
|
||||
for line in text.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
# Data rows have the following format: timestamp CS foF2 QD MUFD QD fmin QD
|
||||
parts = line.split()
|
||||
if len(parts) >= 5:
|
||||
try:
|
||||
# Python 3.8 TZ parsing fudge
|
||||
ts = datetime.fromisoformat(parts[0].replace('Z', '+00:00')).timestamp()
|
||||
ts = datetime.fromisoformat(parts[0].replace("Z", "+00:00")).timestamp()
|
||||
except ValueError:
|
||||
continue
|
||||
try:
|
||||
|
||||
@@ -2,9 +2,12 @@ import logging
|
||||
from xml.etree import ElementTree
|
||||
|
||||
import pytz
|
||||
from dateutil import parser as dateutil_parser, tz as dateutil_tz
|
||||
from dateutil import parser as dateutil_parser
|
||||
from dateutil import tz as dateutil_tz
|
||||
|
||||
from providers.solarconditions.http_solar_conditions_provider import HTTPSolarConditionsProvider
|
||||
from providers.solarconditions.http_solar_conditions_provider import (
|
||||
HTTPSolarConditionsProvider,
|
||||
)
|
||||
|
||||
POLL_INTERVAL = 3600 # 1 hour
|
||||
URL = "https://www.hamqsl.com/solarxml.php"
|
||||
@@ -92,7 +95,8 @@ class HamQSL(HTTPSolarConditionsProvider):
|
||||
"aurora_latitude": float_val("latdegree"),
|
||||
"solar_wind": float_val("solarwind"),
|
||||
"magnetic_field": float_val("magneticfield"),
|
||||
"geomag_field": text("geomagfield").title()
|
||||
"geomag_field": text("geomagfield")
|
||||
.title()
|
||||
.replace("Vr Quiet", "Very Quiet")
|
||||
.replace("Unsettld", "Unsettled")
|
||||
.replace("Min Strm", "Minor Storm")
|
||||
@@ -102,8 +106,10 @@ class HamQSL(HTTPSolarConditionsProvider):
|
||||
"geomag_noise": text("signalnoise"),
|
||||
"hf_conditions": hf_conditions,
|
||||
"vhf_conditions": {
|
||||
"vhf_aurora_northern_hemi": (vhf_map.get(("vhf-aurora", "northern_hemi")) or "").title().replace(
|
||||
"Lat Aur", "Latitude") or None,
|
||||
"vhf_aurora_northern_hemi": (vhf_map.get(("vhf-aurora", "northern_hemi")) or "")
|
||||
.title()
|
||||
.replace("Lat Aur", "Latitude")
|
||||
or None,
|
||||
"es_2m_europe": vhf_map.get(("E-Skip", "europe")),
|
||||
"es_4m_europe": vhf_map.get(("E-Skip", "europe_4m")),
|
||||
"es_6m_europe": vhf_map.get(("E-Skip", "europe_6m")),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from threading import Thread, Event
|
||||
from threading import Event, Thread
|
||||
|
||||
import pytz
|
||||
import requests
|
||||
from requests.exceptions import ConnectionError, ReadTimeout, ConnectTimeout
|
||||
from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout
|
||||
|
||||
from core.constants import HTTP_HEADERS
|
||||
from providers.solarconditions.solar_conditions_provider import SolarConditionsProvider
|
||||
@@ -22,8 +22,7 @@ class HTTPSolarConditionsProvider(SolarConditionsProvider):
|
||||
self._stop_event = Event()
|
||||
|
||||
def start(self):
|
||||
logging.info(
|
||||
f"Set up query of {self.name} solar conditions API every {self._poll_interval!s} seconds.")
|
||||
logging.info(f"Set up query of {self.name} solar conditions API every {self._poll_interval!s} seconds.")
|
||||
self._thread = Thread(target=self._run, name=f"HTTPSolarConditionsProvider-{self.name}")
|
||||
self._thread.start()
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import logging
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from threading import Thread, Event
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from threading import Event, Thread
|
||||
|
||||
import pytz
|
||||
import requests
|
||||
from requests.exceptions import ConnectionError, ReadTimeout, ConnectTimeout
|
||||
from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout
|
||||
|
||||
from core.constants import HTTP_HEADERS
|
||||
from providers.solarconditions.ionosonde_utils import compute_band_states
|
||||
@@ -119,7 +119,7 @@ class KC2GProp(SolarConditionsProvider):
|
||||
except ConnectionError:
|
||||
logging.warning("Connection error when accessing KC2G ionosonde API.")
|
||||
except (ConnectTimeout, ReadTimeout):
|
||||
logging.warning(f"Timeout when accessing KC2G ionosonde API.")
|
||||
logging.warning("Timeout when accessing KC2G ionosonde API.")
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception("Exception in KC2G ionosonde data provider")
|
||||
|
||||
@@ -2,7 +2,9 @@ import logging
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from providers.solarconditions.http_solar_conditions_provider import HTTPSolarConditionsProvider
|
||||
from providers.solarconditions.http_solar_conditions_provider import (
|
||||
HTTPSolarConditionsProvider,
|
||||
)
|
||||
|
||||
POLL_INTERVAL = 10800 # Every 3 hours
|
||||
URL = "https://services.swpc.noaa.gov/text/3-day-forecast.txt"
|
||||
@@ -32,13 +34,13 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
|
||||
# Find the date header line by scanning the next few lines for month & day patterns
|
||||
date_header_idx = None
|
||||
for j in range(start_idx + 1, min(start_idx + 6, len(lines))):
|
||||
if re.search(r'[A-Za-z]{3}\s+\d{2}', lines[j]):
|
||||
if re.search(r"[A-Za-z]{3}\s+\d{2}", lines[j]):
|
||||
date_header_idx = j
|
||||
break
|
||||
if date_header_idx is None:
|
||||
logging.warning(f"NOAA 3-day forecast: could not find date header after '{section_header}'")
|
||||
return None
|
||||
date_matches = re.findall(r'([A-Za-z]{3})\s+(\d{2})', lines[date_header_idx])
|
||||
date_matches = re.findall(r"([A-Za-z]{3})\s+(\d{2})", lines[date_header_idx])
|
||||
if not date_matches:
|
||||
logging.warning(f"NOAA 3-day forecast: no dates in header: {lines[date_header_idx]}")
|
||||
return None
|
||||
@@ -55,20 +57,20 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
|
||||
|
||||
# Parse data rows. Each non-empty line should have a text label followed by percentage values
|
||||
result = {}
|
||||
for line in lines[date_header_idx + 1:]:
|
||||
for line in lines[date_header_idx + 1 :]:
|
||||
line_stripped = line.strip()
|
||||
if not line_stripped:
|
||||
if result:
|
||||
break
|
||||
continue
|
||||
pct_matches = list(re.finditer(r'\b(\d+)%', line_stripped))
|
||||
pct_matches = list(re.finditer(r"\b(\d+)%", line_stripped))
|
||||
if not pct_matches:
|
||||
if result:
|
||||
break
|
||||
continue
|
||||
|
||||
# Row label is everything before the first percentage value
|
||||
row_label = line_stripped[:line_stripped.index(pct_matches[0].group())].strip()
|
||||
row_label = line_stripped[: line_stripped.index(pct_matches[0].group())].strip()
|
||||
row_data = {}
|
||||
for j, match in enumerate(pct_matches):
|
||||
if j >= len(column_timestamps):
|
||||
@@ -94,7 +96,7 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
|
||||
|
||||
# Extract the year from the header line, e.g. "NOAA Kp index breakdown Apr 2-Apr 4, 2026"
|
||||
header_line = lines[start_idx]
|
||||
year_match = re.search(r'\b(\d{4})\b', header_line)
|
||||
year_match = re.search(r"\b(\d{4})\b", header_line)
|
||||
if not year_match:
|
||||
logging.warning(f"NOAA K-index forecast: could not extract year from: {header_line}")
|
||||
return None
|
||||
@@ -106,7 +108,7 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
|
||||
return None
|
||||
|
||||
date_header_line = lines[start_idx + 2]
|
||||
date_matches = re.findall(r'([A-Za-z]{3})\s+(\d{2})', date_header_line)
|
||||
date_matches = re.findall(r"([A-Za-z]{3})\s+(\d{2})", date_header_line)
|
||||
if not date_matches:
|
||||
logging.warning(f"NOAA K-index forecast: could not parse date headers from: {date_header_line}")
|
||||
return None
|
||||
@@ -121,8 +123,8 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
|
||||
|
||||
# Parse each data row, e.g. "00-03UT 2.00 3.00 2.00"
|
||||
k_index_forecast = {}
|
||||
for line in lines[start_idx + 3:]:
|
||||
time_match = re.match(r'^(\d{2})-(\d{2})UT\s+(.*)', line.strip())
|
||||
for line in lines[start_idx + 3 :]:
|
||||
time_match = re.match(r"^(\d{2})-(\d{2})UT\s+(.*)", line.strip())
|
||||
if not time_match:
|
||||
if k_index_forecast:
|
||||
break
|
||||
@@ -130,7 +132,7 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
|
||||
|
||||
start_hour = int(time_match.group(1))
|
||||
# Split on 2 or more spaces so that e.g. "5.67 (G2)" stays as one token per column
|
||||
raw_values = re.split(r' {2,}', time_match.group(3).strip())
|
||||
raw_values = re.split(r" {2,}", time_match.group(3).strip())
|
||||
|
||||
for i, val in enumerate(raw_values):
|
||||
if i >= len(column_dates):
|
||||
@@ -142,7 +144,15 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
|
||||
continue
|
||||
|
||||
date = column_dates[i]
|
||||
start_dt = datetime(date.year, date.month, date.day, start_hour, 0, 0, tzinfo=timezone.utc)
|
||||
start_dt = datetime(
|
||||
date.year,
|
||||
date.month,
|
||||
date.day,
|
||||
start_hour,
|
||||
0,
|
||||
0,
|
||||
tzinfo=timezone.utc,
|
||||
)
|
||||
|
||||
# Key the data dict by start time
|
||||
key = start_dt.timestamp()
|
||||
|
||||
@@ -7,7 +7,7 @@ from core.data_store import DATA_STORE
|
||||
|
||||
class SolarConditionsProvider:
|
||||
"""Generic solar conditions provider class. Subclasses of this query individual APIs for space weather and
|
||||
propagation data."""
|
||||
propagation data."""
|
||||
|
||||
def __init__(self, name, provider_config):
|
||||
"""Constructor"""
|
||||
|
||||
Reference in New Issue
Block a user