First attempt at converting "sig" to "activity" for v3

This commit is contained in:
Ian Renton
2026-09-24 07:09:37 +01:00
parent 1d0129f7bb
commit d91fa70655
90 changed files with 822 additions and 496 deletions
@@ -13,11 +13,10 @@ class ActivityRefDataProvider:
"""Generic activity reference data provider class. Subclasses of this query the individual URLs or files for
data."""
def __init__(self, sig_name, provider_config):
"""Constructor. Note the parameter and attribute are still named "sig_name" for consistency with the API's
"sig" field name."""
def __init__(self, activity_name, provider_config):
"""Constructor"""
self.sig_name = sig_name
self.activity_name = activity_name
self.enabled = provider_config["enabled"]
self.last_update_time = datetime.min.replace(tzinfo=pytz.UTC)
self.status = "Not Started" if self.enabled else "Disabled"
@@ -43,7 +42,7 @@ class ActivityRefDataProvider:
# transact()s is to fail if they can't get the lock (?!). This behaviour is fixed by retry=True.
with DATA_STORE.activity_refs.transact(retry=True):
for d in new_data:
DATA_STORE.activity_refs.set(f"{self.sig_name}:{d.id}", d)
DATA_STORE.activity_refs.set(f"{self.activity_name}:{d.id}", d)
# For the big data sources, loading will take a few minutes. If we want to shut down the software neatly
# within the first few minutes of startup, we need a way to abort this expensive process of filling up the
@@ -52,4 +51,4 @@ class ActivityRefDataProvider:
break
self.reference_count = len(new_data)
logger.info(f"Loaded {self.reference_count} references for {self.sig_name} into the data store.")
logger.info(f"Loaded {self.reference_count} references for {self.activity_name} into the data store.")
+1 -1
View File
@@ -25,7 +25,7 @@ class ARLHS(FileDownloadActivityRefDataProvider):
ref_id = row["ARLHS"]
new_data.append(
ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=ref_id,
name=row.get("Name", None),
ref_type=ActivityRefType.LIGHTHOUSE,
+1 -1
View File
@@ -30,7 +30,7 @@ class COTA(FileDownloadActivityRefDataProvider):
new_data.append(
ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=ref_id,
name=name,
ref_type=ActivityRefType.CASTLE,
+6 -1
View File
@@ -27,7 +27,12 @@ class DCE(FileDownloadActivityRefDataProvider):
for index, row in df.iterrows():
if row.iloc[0] and row.iloc[2]:
new_data.append(
ActivityRef(sig=self.ACTIVITY, id=row.iloc[0].strip(), name=row.iloc[2].strip(), ref_type=ActivityRefType.CASTLE)
ActivityRef(
activity=self.ACTIVITY,
id=row.iloc[0].strip(),
name=row.iloc[2].strip(),
ref_type=ActivityRefType.CASTLE,
)
)
# Bail out if a stop has been requested, i.e. the program is shutting down - no need to parse the rest of
+6 -1
View File
@@ -31,7 +31,12 @@ class DEFE(FileDownloadActivityRefDataProvider):
if row.iloc[0] and row.iloc[1]:
new_data.append(
ActivityRef(sig=self.ACTIVITY, id=row.iloc[0].strip(), name=row.iloc[1].strip(), ref_type=ActivityRefType.BUILDING)
ActivityRef(
activity=self.ACTIVITY,
id=row.iloc[0].strip(),
name=row.iloc[1].strip(),
ref_type=ActivityRefType.BUILDING,
)
)
# Bail out if a stop has been requested, i.e. the program is shutting down - no need to parse the rest of
+1 -1
View File
@@ -40,7 +40,7 @@ class DME(LocalFileActivityRefDataProvider):
)
ref = ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=ref_id,
ref_type=ActivityRefType.TOWN,
name=f"{row['NOMBRE_ACTUAL']}, {row['PROVINCIA']}",
+6 -1
View File
@@ -22,7 +22,12 @@ class DMUE(FileDownloadActivityRefDataProvider):
for row in csv.reader(http_response.content.decode("utf-8-sig").splitlines(), delimiter=";"):
if len(row) > 1 and row[0] and row[1]:
new_data.append(
ActivityRef(sig=self.ACTIVITY, id=row[0].strip(), name=row[1].strip(), ref_type=ActivityRefType.BUILDING)
ActivityRef(
activity=self.ACTIVITY,
id=row[0].strip(),
name=row[1].strip(),
ref_type=ActivityRefType.BUILDING,
)
)
# Bail out if a stop has been requested, i.e. the program is shutting down - no need to parse the rest of
+5 -1
View File
@@ -36,7 +36,11 @@ class DMVE(FileDownloadActivityRefDataProvider):
continue
if ref and name:
new_data.append(ActivityRef(sig=self.ACTIVITY, id=ref.strip(), name=name.strip(), ref_type=ActivityRefType.BUILDING))
new_data.append(
ActivityRef(
activity=self.ACTIVITY, id=ref.strip(), name=name.strip(), ref_type=ActivityRefType.BUILDING
)
)
# Bail out if a stop has been requested, i.e. the program is shutting down - no need to parse the rest of
# the data in this case
+3 -1
View File
@@ -21,7 +21,9 @@ class DTMBA(FileDownloadActivityRefDataProvider):
split = row.split(";")
ref_id = split[0]
ref_name = split[1]
new_data.append(ActivityRef(sig=self.ACTIVITY, id=ref_id, name=ref_name, ref_type=ActivityRefType.BUILDING))
new_data.append(
ActivityRef(activity=self.ACTIVITY, id=ref_id, name=ref_name, ref_type=ActivityRefType.BUILDING)
)
# Bail out if a stop has been requested, i.e. the program is shutting down - no need to parse the rest of
# the data in this case
+10 -2
View File
@@ -41,8 +41,16 @@ class FEA(FileDownloadActivityRefDataProvider):
# prefix and just use FEA-1234 or FEA 1234, so we add both copies to the database.
ref_id_1 = row[0].strip()
ref_id_2 = ref_id_1.replace("D-", "FEA-").replace("E-", "FEA-")
new_data.append(ActivityRef(sig=self.ACTIVITY, id=ref_id_1, name=row[1].strip(), ref_type=ActivityRefType.LIGHTHOUSE))
new_data.append(ActivityRef(sig=self.ACTIVITY, id=ref_id_2, name=row[1].strip(), ref_type=ActivityRefType.LIGHTHOUSE))
new_data.append(
ActivityRef(
activity=self.ACTIVITY, id=ref_id_1, name=row[1].strip(), ref_type=ActivityRefType.LIGHTHOUSE
)
)
new_data.append(
ActivityRef(
activity=self.ACTIVITY, id=ref_id_2, name=row[1].strip(), ref_type=ActivityRefType.LIGHTHOUSE
)
)
# Bail out if a stop has been requested, i.e. the program is shutting down - no need to parse the rest of
# the data in this case
@@ -16,19 +16,21 @@ class FileDownloadActivityRefDataProvider(ActivityRefDataProvider):
"""Generic activity ref data provider class for providers that fetch their data from the web by downloading a
file."""
def __init__(self, sig_name, provider_config, url, poll_interval):
def __init__(self, activity_name, provider_config, url, poll_interval):
"""Set up the provider, note poll_interval is in *days*."""
super().__init__(sig_name, provider_config)
super().__init__(activity_name, provider_config)
self._url = url
self._poll_interval = poll_interval
self._thread = None
self._url_data_cache = URLDataCache(f"activity_ref_data_{sig_name}")
self._url_data_cache = URLDataCache(f"activity_ref_data_{activity_name}")
def start(self):
# Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between
# subsequent polls, so start() returns immediately and the application can continue starting.
logger.info(f"Set up query of {self.sig_name} activity ref data every {self._poll_interval!s} days.")
self._thread = Thread(target=self._run, name=f"FileDownloadActivityRefDataProvider-{self.sig_name}", daemon=True)
logger.info(f"Set up query of {self.activity_name} activity ref data every {self._poll_interval!s} days.")
self._thread = Thread(
target=self._run, name=f"FileDownloadActivityRefDataProvider-{self.activity_name}", daemon=True
)
self._thread.start()
def stop(self):
@@ -36,7 +38,9 @@ class FileDownloadActivityRefDataProvider(ActivityRefDataProvider):
if self._thread:
self._thread.join(timeout=12)
if self._thread.is_alive():
logger.warning(f"{self.sig_name} activity ref data worker thread did not exit on time and will be killed.")
logger.warning(
f"{self.activity_name} activity ref data worker thread did not exit on time and will be killed."
)
def _run(self):
while True:
@@ -48,7 +52,7 @@ class FileDownloadActivityRefDataProvider(ActivityRefDataProvider):
try:
# Request data from API. Use the data cache (with a TTL of 1 day) here, not as the main mechanism for
# caching, but just so continual restarts of the software during testing don't hammer the servers.
logger.debug(f"Downloading {self.sig_name} activity ref data...")
logger.debug(f"Downloading {self.activity_name} activity ref data...")
http_response = self._url_data_cache.get(self._url, headers=HTTP_HEADERS)
# Check response code was good
if http_response.ok:
@@ -60,20 +64,22 @@ class FileDownloadActivityRefDataProvider(ActivityRefDataProvider):
self.status = "OK"
self.last_update_time = datetime.now(pytz.UTC)
logger.debug(f"Received activity ref data for {self.sig_name}")
logger.debug(f"Received activity ref data for {self.activity_name}")
else:
self.status = "Error"
logger.warning(f"HTTP {http_response.status_code} when downloading activity ref data for {self.sig_name}.")
logger.warning(
f"HTTP {http_response.status_code} when downloading activity ref data for {self.activity_name}."
)
except ConnectionError:
self.status = "Error"
logger.warning(f"Connection error when downloading activity ref data for {self.sig_name}.")
logger.warning(f"Connection error when downloading activity ref data for {self.activity_name}.")
except (ConnectTimeout, ReadTimeout):
self.status = "Error"
logger.warning(f"Timeout when downloading activity ref data for {self.sig_name}.")
logger.warning(f"Timeout when downloading activity ref data for {self.activity_name}.")
except Exception:
self.status = "Error"
logger.exception(f"Exception in HTTP Activity Ref Data Provider ({self.sig_name})")
logger.exception(f"Exception in HTTP Activity Ref Data Provider ({self.activity_name})")
self._stop_event.wait(timeout=1)
def _http_response_to_data(self, http_response):
+1 -1
View File
@@ -24,7 +24,7 @@ class GMA(FileDownloadActivityRefDataProvider):
ref_id = row["Reference"]
new_data.append(
ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=ref_id,
name=row.get("Name", None),
ref_type=ActivityRefType.SUMMIT,
+1 -1
View File
@@ -25,7 +25,7 @@ class ILLW(FileDownloadActivityRefDataProvider):
ref_id = row["ILLW"]
new_data.append(
ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=ref_id,
name=row.get("Name", None),
ref_type=ActivityRefType.LIGHTHOUSE,
+1 -1
View File
@@ -42,7 +42,7 @@ class IOTA(FileDownloadActivityRefDataProvider):
new_data.append(
ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=ref_id,
name=ref["name"],
ref_type=ActivityRefType.ISLAND,
+1 -1
View File
@@ -30,7 +30,7 @@ class LLOTA(FileDownloadActivityRefDataProvider):
new_data.append(
ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=ref_id,
name=str(ref["name"]),
ref_type=ActivityRefType.LAKE,
@@ -11,12 +11,12 @@ logger = logging.getLogger(__name__)
class LocalFileActivityRefDataProvider(ActivityRefDataProvider):
"""Generic activity ref data provider class for providers that fetch their data from a local file on startup."""
def __init__(self, sig_name, provider_config, path):
super().__init__(sig_name, provider_config)
def __init__(self, activity_name, provider_config, path):
super().__init__(activity_name, provider_config)
self._path = path
def start(self):
logger.debug(f"Loading {self.sig_name} activity ref data from file.")
logger.debug(f"Loading {self.activity_name} activity ref data from file.")
try:
new_data = self._file_to_data(self._path)
if new_data:
@@ -25,10 +25,10 @@ class LocalFileActivityRefDataProvider(ActivityRefDataProvider):
self.last_update_time = datetime.now(pytz.UTC)
else:
self.status = "Error"
logger.info(f"Failed to load activity ref data for {self.sig_name}")
logger.info(f"Failed to load activity ref data for {self.activity_name}")
except Exception:
self.status = "Error"
logger.exception(f"Exception in local file Activity Ref Data Provider ({self.sig_name})")
logger.exception(f"Exception in local file Activity Ref Data Provider ({self.activity_name})")
def _file_to_data(self, path):
"""Load a file on the given path and turn it into activity ref data."""
+1 -1
View File
@@ -24,7 +24,7 @@ class MOTA(FileDownloadActivityRefDataProvider):
ref_id = row["Reference"]
new_data.append(
ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=ref_id,
name=row.get("Name", None),
ref_type=ActivityRefType.MILL,
+1 -1
View File
@@ -39,7 +39,7 @@ class PGA(FileDownloadActivityRefDataProvider):
new_data.append(
ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=ref_id,
name=name,
ref_type=ActivityRefType.REGION,
@@ -17,9 +17,9 @@ class ParksNPeaksKMLActivityRefDataProvider(FileDownloadActivityRefDataProvider)
REF_PATTERN = re.compile(r"VKFF-\d+")
def __init__(self, sig_name, provider_config, url, poll_interval):
def __init__(self, activity_name, provider_config, url, poll_interval):
"""Set up the provider, note poll_interval is in *days*."""
super().__init__(sig_name, provider_config, url, poll_interval)
super().__init__(activity_name, provider_config, url, poll_interval)
def _http_response_to_data(self, http_response):
new_data = []
@@ -41,7 +41,7 @@ class ParksNPeaksKMLActivityRefDataProvider(FileDownloadActivityRefDataProvider)
longitude, latitude = placemark.geometry.x, placemark.geometry.y
ref = ActivityRef(
sig=self.sig_name,
activity=self.activity_name,
id=ref_id,
name=placemark.name,
ref_type=ActivityRefType.PARK,
+1 -1
View File
@@ -24,7 +24,7 @@ class POTA(FileDownloadActivityRefDataProvider):
ref_id = row["reference"]
new_data.append(
ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=ref_id,
name=row.get("name", None),
ref_type=ActivityRefType.PARK,
+1 -1
View File
@@ -24,7 +24,7 @@ class SIOTA(FileDownloadActivityRefDataProvider):
ref_id = row["SILO_CODE"]
new_data.append(
ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=ref_id,
name=row.get("NAME", None),
ref_type=ActivityRefType.SILO,
+1 -1
View File
@@ -28,7 +28,7 @@ class SOTA(FileDownloadActivityRefDataProvider):
longitude = float(row["Longitude"]) if "Longitude" in row and row["Longitude"] != "" else None
altitude = float(row["AltM"]) if "AltM" in row and row["AltM"] != "" else None
ref = ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=ref_id,
name=row.get("SummitName", None),
ref_type=ActivityRefType.SUMMIT,
+1 -1
View File
@@ -24,7 +24,7 @@ class Toilets(LocalFileActivityRefDataProvider):
for row in dr:
new_data.append(
ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=row["ref"],
name=row["ref"],
ref_type=ActivityRefType.TOILET,
+1 -1
View File
@@ -24,7 +24,7 @@ class Towers(FileDownloadActivityRefDataProvider):
ref_id = row["Ref"]
new_data.append(
ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=ref_id,
name=row.get("Nazev", None),
ref_type=ActivityRefType.TOWER,
+1 -1
View File
@@ -43,7 +43,7 @@ class WCA(FileDownloadActivityRefDataProvider):
new_data.append(
ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=ref_id,
name=row.get("CLEAN NAME", None),
ref_type=ActivityRefType.CASTLE,
+1 -1
View File
@@ -30,7 +30,7 @@ class WOTA(FileDownloadActivityRefDataProvider):
new_data.append(
ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=ref_id,
name=feature["properties"]["title"],
url=url,
+1 -1
View File
@@ -24,7 +24,7 @@ class WWBOTA(FileDownloadActivityRefDataProvider):
ref_id = row["Reference"]
new_data.append(
ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=ref_id,
name=row.get("Name", None),
ref_type=ActivityRefType.BUNKER,
+1 -1
View File
@@ -24,7 +24,7 @@ class WWFF(FileDownloadActivityRefDataProvider):
ref_id = row["reference"]
new_data.append(
ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=ref_id,
name=row.get("name", None),
ref_type=ActivityRefType.PARK,
+1 -1
View File
@@ -33,7 +33,7 @@ class ZLOTA(FileDownloadActivityRefDataProvider):
ref_type = None
new_ref = ActivityRef(
sig=self.ACTIVITY,
activity=self.ACTIVITY,
id=ref_id,
name=ref["name"],
ref_type=ref_type,
+2 -2
View File
@@ -56,8 +56,8 @@ class BOTA(HTTPAlertProvider):
alert = Alert(
source=self.name,
dx_calls=[dx_call],
sig=ActivityName.BOTA,
sig_refs=[ActivityRef(id=ref_name, sig=ActivityName.BOTA)],
activity=ActivityName.BOTA,
activity_refs=[ActivityRef(id=ref_name, activity=ActivityName.BOTA)],
start_time=date_time.timestamp(),
)
+3 -3
View File
@@ -38,11 +38,11 @@ class Hamsat(HTTPAlertProvider):
dx_grid=source_alert["grids"][0],
freqs_modes=freqs_modes,
comment=source_alert["comment"],
sig=ActivityName.SATELLITE,
activity=ActivityName.SATELLITE,
# Fudge an activity ref to provide the remaining bits of data we need: the satellite and the operator's grid
sig_refs=[
activity_refs=[
ActivityRef(
sig=ActivityName.SATELLITE,
activity=ActivityName.SATELLITE,
id=source_alert["satellite"]["name"],
)
],
+1 -1
View File
@@ -89,7 +89,7 @@ class NG3K(HTTPAlertProvider):
comment=f"{by}; {comment}; {qsl_info}",
start_time=start_timestamp,
end_time=end_timestamp,
sig=ActivityName.DXPEDITION,
activity=ActivityName.DXPEDITION,
)
# Add to our list.
+3 -3
View File
@@ -37,7 +37,7 @@ class ParksNPeaks(HTTPAlertProvider):
datetime.strptime(source_alert["alTime"], "%Y-%m-%d %H:%M:%S").replace(tzinfo=pytz.UTC).timestamp()
)
activity_refs = [ActivityRef(id=ref_id, sig=activity, name=ref_name)]
activity_refs = [ActivityRef(id=ref_id, activity=activity, name=ref_name)]
# Convert to our alert format
alert = Alert(
@@ -46,8 +46,8 @@ class ParksNPeaks(HTTPAlertProvider):
dx_calls=[source_alert["CallSign"].upper()],
freqs_modes=f"{source_alert['Freq']} {source_alert['MODE']}",
comment=source_alert["Comments"],
sig=activity,
sig_refs=activity_refs,
activity=activity,
activity_refs=activity_refs,
start_time=start_time,
)
+3 -3
View File
@@ -28,11 +28,11 @@ class POTA(HTTPAlertProvider):
dx_calls=[source_alert["activator"].upper()],
freqs_modes=source_alert["frequencies"],
comment=source_alert["comments"],
sig=ActivityName.POTA,
sig_refs=[
activity=ActivityName.POTA,
activity_refs=[
ActivityRef(
id=source_alert["reference"],
sig=ActivityName.POTA,
activity=ActivityName.POTA,
name=source_alert["name"],
url=f"https://pota.app/#/park/{source_alert['reference']}",
)
+1 -1
View File
@@ -69,7 +69,7 @@ class RSGBICALAlertProvider(ICALAlertProvider):
comment=summary,
start_time=start_timestamp,
end_time=end_timestamp,
sig=ActivityName.CONTEST,
activity=ActivityName.CONTEST,
)
return alert
+3 -3
View File
@@ -34,11 +34,11 @@ class SOTA(HTTPAlertProvider):
dx_names=[source_alert["activatorName"].upper()],
freqs_modes=source_alert["frequency"],
comment=source_alert["comments"],
sig=ActivityName.SOTA,
sig_refs=[
activity=ActivityName.SOTA,
activity_refs=[
ActivityRef(
id=f"{source_alert['associationCode']}/{source_alert['summitCode']}",
sig=ActivityName.SOTA,
activity=ActivityName.SOTA,
name=summit_name,
activation_score=summit_points,
)
+1 -1
View File
@@ -35,7 +35,7 @@ class WA7BNM(ICALAlertProvider):
url=url,
start_time=start_timestamp,
end_time=end_timestamp,
sig=ActivityName.CONTEST,
activity=ActivityName.CONTEST,
)
return alert
+1 -1
View File
@@ -75,7 +75,7 @@ class WOTA(HTTPAlertProvider):
dx_calls=[dx_call],
freqs_modes=freqs_modes,
comment=comment,
sig_refs=[ActivityRef(id=ref, sig=ActivityName.WOTA, name=ref_name)] if ref else [],
activity_refs=[ActivityRef(id=ref, activity=ActivityName.WOTA, name=ref_name)] if ref else [],
start_time=time.timestamp(),
)
+2 -2
View File
@@ -28,8 +28,8 @@ class WWFF(HTTPAlertProvider):
dx_calls=[source_alert["activator_call"].upper()],
freqs_modes=f"{source_alert['band']} {source_alert['mode']}",
comment=source_alert["remarks"],
sig=ActivityName.WWFF,
sig_refs=[ActivityRef(id=source_alert["reference"], sig=ActivityName.WWFF)],
activity=ActivityName.WWFF,
activity_refs=[ActivityRef(id=source_alert["reference"], activity=ActivityName.WWFF)],
start_time=datetime.strptime(source_alert["utc_start"], "%Y-%m-%d %H:%M:%S")
.replace(tzinfo=pytz.UTC)
.timestamp(),
+35 -35
View File
@@ -68,10 +68,10 @@ class GMA(HTTPSpotProvider):
# Filter out some weird mode strings
mode=Mode.from_name(source_spot["MODE"].upper()) if "<>" not in source_spot["MODE"] else None,
comment=source_spot["TEXT"],
sig_refs=[
activity_refs=[
ActivityRef(
id=source_spot["REF"],
sig="",
activity="",
name=source_spot["NAME"],
latitude=lat,
longitude=lon,
@@ -98,57 +98,57 @@ class GMA(HTTPSpotProvider):
and ref_response.text != "\n"
):
ref_info = ref_response.json()
if spot.sig_refs and ref_info and "reftype" in ref_info:
if spot.activity_refs and ref_info and "reftype" in ref_info:
match ref_info["reftype"]:
case "Summit":
# Summits are a bit complicated, they can be SOTA or GMA depending on the
# separate "sota" field:
if "sota" in ref_info and ref_info["sota"] != "":
spot.sig_refs[0].sig = ActivityName.SOTA
spot.sig_refs[0].ref_type = ActivityRefType.SUMMIT
spot.sig = ActivityName.SOTA
spot.activity_refs[0].activity = ActivityName.SOTA
spot.activity_refs[0].ref_type = ActivityRefType.SUMMIT
spot.activity = ActivityName.SOTA
else:
spot.sig_refs[0].sig = ActivityName.GMA
spot.sig_refs[0].ref_type = ActivityRefType.SUMMIT
spot.sig = ActivityName.GMA
spot.activity_refs[0].activity = ActivityName.GMA
spot.activity_refs[0].ref_type = ActivityRefType.SUMMIT
spot.activity = ActivityName.GMA
case "POTA":
spot.sig_refs[0].sig = ActivityName.POTA
spot.sig_refs[0].ref_type = ActivityRefType.PARK
spot.sig = ActivityName.POTA
spot.activity_refs[0].activity = ActivityName.POTA
spot.activity_refs[0].ref_type = ActivityRefType.PARK
spot.activity = ActivityName.POTA
case "WWFF":
spot.sig_refs[0].sig = ActivityName.WWFF
spot.sig_refs[0].ref_type = ActivityRefType.PARK
spot.sig = ActivityName.WWFF
spot.activity_refs[0].activity = ActivityName.WWFF
spot.activity_refs[0].ref_type = ActivityRefType.PARK
spot.activity = ActivityName.WWFF
case "IOTA Island":
spot.sig_refs[0].sig = ActivityName.IOTA
spot.sig_refs[0].ref_type = ActivityRefType.ISLAND
spot.sig = ActivityName.IOTA
spot.activity_refs[0].activity = ActivityName.IOTA
spot.activity_refs[0].ref_type = ActivityRefType.ISLAND
spot.activity = ActivityName.IOTA
case "GMA Island":
spot.sig_refs[0].sig = ActivityName.GMA_ISLANDS
spot.sig_refs[0].ref_type = ActivityRefType.ISLAND
spot.sig = ActivityName.GMA_ISLANDS
spot.activity_refs[0].activity = ActivityName.GMA_ISLANDS
spot.activity_refs[0].ref_type = ActivityRefType.ISLAND
spot.activity = ActivityName.GMA_ISLANDS
case "Lighthouse (ILLW)":
spot.sig_refs[0].sig = ActivityName.ILLW
spot.sig_refs[0].ref_type = ActivityRefType.LIGHTHOUSE
spot.sig = ActivityName.ILLW
spot.activity_refs[0].activity = ActivityName.ILLW
spot.activity_refs[0].ref_type = ActivityRefType.LIGHTHOUSE
spot.activity = ActivityName.ILLW
case "Lighthouse (ARLHS)":
spot.sig_refs[0].sig = ActivityName.ARLHS
spot.sig_refs[0].ref_type = ActivityRefType.LIGHTHOUSE
spot.sig = ActivityName.ARLHS
spot.activity_refs[0].activity = ActivityName.ARLHS
spot.activity_refs[0].ref_type = ActivityRefType.LIGHTHOUSE
spot.activity = ActivityName.ARLHS
case "Castle":
spot.sig_refs[0].sig = ActivityName.WCA
spot.sig_refs[0].ref_type = ActivityRefType.CASTLE
spot.sig = ActivityName.WCA
spot.activity_refs[0].activity = ActivityName.WCA
spot.activity_refs[0].ref_type = ActivityRefType.CASTLE
spot.activity = ActivityName.WCA
case "Mill":
spot.sig_refs[0].sig = ActivityName.MOTA
spot.sig_refs[0].ref_type = ActivityRefType.MILL
spot.sig = ActivityName.MOTA
spot.activity_refs[0].activity = ActivityName.MOTA
spot.activity_refs[0].ref_type = ActivityRefType.MILL
spot.activity = ActivityName.MOTA
case _:
logger.warning(
f"GMA spot found with ref type {ref_info['reftype']}, developer needs to add support for this!"
)
spot.sig_refs[0].sig = ref_info["reftype"]
spot.sig = ref_info["reftype"]
spot.activity_refs[0].activity = ref_info["reftype"]
spot.activity = ref_info["reftype"]
elif not ref_response.from_cache:
if not ref_response.ok:
+3 -3
View File
@@ -62,11 +62,11 @@ class HEMA(HTTPSpotProvider):
freq=float(freq_mode_match.group(1)) * 1000000,
mode=Mode.from_name(freq_mode_match.group(2).upper()),
comment=spotter_comment_match.group(2),
sig=ActivityName.HEMA,
sig_refs=[
activity=ActivityName.HEMA,
activity_refs=[
ActivityRef(
id=spot_items[3].upper(),
sig=ActivityName.HEMA,
activity=ActivityName.HEMA,
name=spot_items[4],
latitude=float(spot_items[7]),
longitude=float(spot_items[8]),
+3 -3
View File
@@ -34,11 +34,11 @@ class LLOTA(HTTPSpotProvider):
freq=float(source_spot["frequency"]) * 1000000,
mode=Mode.from_name(source_spot["mode"].upper()),
comment=comment,
sig=ActivityName.LLOTA,
sig_refs=[
activity=ActivityName.LLOTA,
activity_refs=[
ActivityRef(
id=source_spot["reference"],
sig=ActivityName.LLOTA,
activity=ActivityName.LLOTA,
name=source_spot["reference_name"],
ref_type=ActivityRefType.LAKE,
)
+5 -5
View File
@@ -70,20 +70,20 @@ class ParksNPeaks(HTTPSpotProvider):
ref_id = source_spot["actSiteID"]
if activity:
spot.sig = activity
spot.activity = activity
if ref_id:
activity_refs = [
ActivityRef(
id=ref_id,
sig=activity,
activity=activity,
# Free text location is not present in all spots, so only add it if it's set
name=source_spot["actLocation"]
if "actLocation" in source_spot and source_spot["actLocation"] != ""
else None,
)
]
spot.sig_refs = activity_refs
spot.activity_refs = activity_refs
else:
# If no actSiteID is set, e.g. because actClass is "QRP", sometimes we still have an actLocation
@@ -128,9 +128,9 @@ class ParksNPeaks(HTTPSpotProvider):
raise ValueError(
"Parks N Peaks user ID and API key are required. Get yours from your Parks N Peaks account."
)
ref_id = spot.sig_refs[0].id if spot.sig_refs else ""
ref_id = spot.activity_refs[0].id if spot.activity_refs else ""
body = {
"actClass": spot.sig or "",
"actClass": spot.activity or "",
"actCallsign": spot.dx_call,
"actSite": ref_id,
"mode": spot.mode or "",
+6 -6
View File
@@ -33,11 +33,11 @@ class POTA(HTTPSpotProvider):
freq=float(source_spot["frequency"]) * 1000 if source_spot["frequency"] != "INVALID" else None,
mode=Mode.from_name(source_spot["mode"].upper()),
comment=source_spot["comments"],
sig=ActivityName.POTA,
sig_refs=[
activity=ActivityName.POTA,
activity_refs=[
ActivityRef(
id=source_spot["reference"],
sig=ActivityName.POTA,
activity=ActivityName.POTA,
name=source_spot["name"],
latitude=source_spot["latitude"],
longitude=source_spot["longitude"],
@@ -61,14 +61,14 @@ class POTA(HTTPSpotProvider):
return activity == ActivityName.POTA
def submit_spot(self, spot, credentials):
sig_ref = spot.sig_refs[0].id if spot.sig_refs else None
if sig_ref:
ref_id = spot.activity_refs[0].id if spot.activity_refs else None
if ref_id:
body = {
"activator": spot.dx_call,
"spotter": spot.de_call,
"frequency": str(spot.freq / 1000.0),
"mode": spot.mode or "",
"reference": sig_ref,
"reference": ref_id,
"comments": spot.comment or "",
"source": "Spothole",
}
+6 -6
View File
@@ -57,11 +57,11 @@ class SOTA(HTTPSpotProvider):
# Seen SOTA spots with no frequency!
mode=Mode.from_name(source_spot["mode"].upper()),
comment=source_spot["comments"],
sig=ActivityName.SOTA,
sig_refs=[
activity=ActivityName.SOTA,
activity_refs=[
ActivityRef(
id=source_spot["summitCode"],
sig=ActivityName.SOTA,
activity=ActivityName.SOTA,
name=source_spot["summitName"],
latitude=source_spot["latitude"],
longitude=source_spot["longitude"],
@@ -92,10 +92,10 @@ class SOTA(HTTPSpotProvider):
id_token = credentials.get("id_token", "")
if not access_token or not id_token:
raise ValueError("SOTA API tokens are required. Please log into SOTA in order to spot to it.")
sig_ref = spot.sig_refs[0].id if spot.sig_refs else ""
if sig_ref:
ref_id = spot.activity_refs[0].id if spot.activity_refs else ""
if ref_id:
# Split reference into association and summit codes
ref_split = sig_ref.split("/")
ref_split = ref_id.split("/")
# Figure out a valid mode. Borrowed this from PoLo :)
# https://github.com/ham2k/app-polo/blob/main/src/extensions/activities/sota/SOTAPostSelfSpot.js
+3 -3
View File
@@ -59,13 +59,13 @@ class Tiles(HTTPSpotProvider):
freq=freq,
mode=Mode.from_name(source_spot["mode"].upper()),
comment=source_spot["notes"],
sig=ActivityName.TILES,
activity=ActivityName.TILES,
# Tiles spots can include POTA & SOTA references, but ignore those on the basis that we will get them separately from the POTA/SOTA providers anyway.
# Just take the grid reference itself as the single Tiles activity reference.
sig_refs=[
activity_refs=[
ActivityRef(
id=source_spot["maidenhead_grid"],
sig=ActivityName.TILES,
activity=ActivityName.TILES,
name=source_spot["maidenhead_grid"],
latitude=source_spot["latitude"],
longitude=source_spot["longitude"],
+4 -2
View File
@@ -34,8 +34,10 @@ class Towers(HTTPSpotProvider):
dx_call=source_spot["call"].upper(),
freq=likely_freq,
comment=source_spot["comment"],
sig=ActivityName.TOWERS,
sig_refs=[ActivityRef(id=source_spot["ref"], sig=ActivityName.TOWERS, ref_type=ActivityRefType.TOWER)],
activity=ActivityName.TOWERS,
activity_refs=[
ActivityRef(id=source_spot["ref"], activity=ActivityName.TOWERS, ref_type=ActivityRefType.TOWER)
],
time=datetime.strptime(response_json["updated"][:10] + source_spot["time"], "%Y-%m-%d%H:%M")
.replace(tzinfo=pytz.utc)
.timestamp(),
+7 -3
View File
@@ -92,9 +92,13 @@ class WOTA(HTTPSpotProvider):
freq=freq_hz,
mode=Mode.from_name(mode),
comment=comment,
sig=ActivityName.WOTA,
sig_refs=(
[ActivityRef(id=ref, sig=ActivityName.WOTA, name=ref_name, ref_type=ActivityRefType.SUMMIT)]
activity=ActivityName.WOTA,
activity_refs=(
[
ActivityRef(
id=ref, activity=ActivityName.WOTA, name=ref_name, ref_type=ActivityRefType.SUMMIT
)
]
if ref
else []
),
+3 -3
View File
@@ -23,7 +23,7 @@ class WWBOTA(SSESpotProvider):
for ref in source_spot["references"]:
activity_ref = ActivityRef(
id=ref["reference"],
sig=ActivityName.WWBOTA,
activity=ActivityName.WWBOTA,
name=ref["name"],
latitude=ref["lat"],
longitude=ref["long"],
@@ -38,8 +38,8 @@ class WWBOTA(SSESpotProvider):
freq=float(source_spot["freq"]) * 1000000,
mode=Mode.from_name(source_spot["mode"].upper()) if source_spot.get("mode") else None,
comment=source_spot["comment"],
sig=ActivityName.WWBOTA,
sig_refs=refs,
activity=ActivityName.WWBOTA,
activity_refs=refs,
time=datetime.fromisoformat(source_spot["time"].replace("Z", "+00:00")).timestamp(),
# WWBOTA spots can contain multiple references for bunkers being activated simultaneously. For
# now, we will just pick the first one to use as our grid, latitude and longitude.
+3 -3
View File
@@ -30,11 +30,11 @@ class WWFF(HTTPSpotProvider):
freq=float(source_spot["frequency_khz"]) * 1000,
mode=Mode.from_name(source_spot["mode"].upper()),
comment=source_spot["remarks"],
sig=ActivityName.WWFF,
sig_refs=[
activity=ActivityName.WWFF,
activity_refs=[
ActivityRef(
id=source_spot["reference"],
sig=ActivityName.WWFF,
activity=ActivityName.WWFF,
name=source_spot["reference_name"],
latitude=source_spot["latitude"],
longitude=source_spot["longitude"],
+9 -7
View File
@@ -14,16 +14,18 @@ class XOTA(WebsocketSpotProvider):
The provider typically doesn't give us a lat/lon or activity explicitly, so our own config provides an activity
which we can then use for lookups. This functionality is implemented for Toilets on the Air events, of which
there are several - so a plain lookup of a "TOTA reference" doesn't make sense, it depends on which TOTA, which
is why we also provide a sig_ref_prefix in our config. This is applied to the reference ID, so e.g. "T-01" at C3
might become "C3 T-01". This allows us to provide location lookups for TOTA at several conferences."""
is why we also provide an activity_ref_prefix in our config. This is applied to the reference ID, so e.g. "T-01"
at C3 might become "C3 T-01". This allows us to provide location lookups for TOTA at several conferences."""
ACTIVITY = None
def __init__(self, provider_config):
name = provider_config.get("name", "xOTA")
super().__init__(name, provider_config, provider_config["url"])
self.ACTIVITY = str(provider_config["sig"]) if "sig" in provider_config else None
self._activity_ref_prefix = str(provider_config["sig_ref_prefix"]) if "sig_ref_prefix" in provider_config else ""
self.ACTIVITY = str(provider_config["activity"]) if "activity" in provider_config else None
self._activity_ref_prefix = (
str(provider_config["activity_ref_prefix"]) if "activity_ref_prefix" in provider_config else ""
)
def _ws_message_to_spot(self, b):
string = b.decode("utf-8")
@@ -35,11 +37,11 @@ class XOTA(WebsocketSpotProvider):
dx_call=source_spot["stationCallSign"].upper(),
freq=float(source_spot["freq"]) * 1000,
mode=Mode.from_name(source_spot["mode"].upper()),
sig=self.ACTIVITY,
sig_refs=[
activity=self.ACTIVITY,
activity_refs=[
ActivityRef(
id=ref_id,
sig=self.ACTIVITY or "",
activity=self.ACTIVITY or "",
url=source_spot["reference"]["website"],
)
],
+3 -3
View File
@@ -35,11 +35,11 @@ class ZLOTA(HTTPSpotProvider):
freq=freq_hz,
mode=Mode.from_name(source_spot["mode"].upper().strip()),
comment=source_spot["comments"],
sig=ActivityName.ZLOTA,
sig_refs=[
activity=ActivityName.ZLOTA,
activity_refs=[
ActivityRef(
id=source_spot["reference"],
sig=ActivityName.ZLOTA,
activity=ActivityName.ZLOTA,
name=source_spot["name"],
)
],