Refactor activity names to use an enum instead of a string to avoid typos #147

This commit is contained in:
Ian Renton
2026-09-18 18:09:41 +01:00
parent ab79e7e01c
commit e5caf7353d
61 changed files with 735 additions and 638 deletions
+444
View File
@@ -0,0 +1,444 @@
from core.enums import ActivityName, ActivityType, ActivityRefType
from data.activity import Activity
ACTIVITIES: dict[ActivityName, Activity] = {
ActivityName.CONTEST: Activity(
name=ActivityName.CONTEST,
# No sensible way to determine *which* contest, but if we set comment_names=["CONTEST"] then at least
# any spots with "contest" in the comment will get allocated to this activity.
comment_names=["CONTEST"],
description="Contest",
sig_type=ActivityType.TRADITIONAL,
icon="fa-trophy",
refs_globally_unique=False,
),
ActivityName.DXPEDITION: Activity(
name=ActivityName.DXPEDITION,
# DXpedition stations are never really spotted with "DXpedition" in the comments, but we can assign
# this activity to a spot other ways.
comment_names=[],
description="Radio expedition to a remote location",
sig_type=ActivityType.TRADITIONAL,
icon="fa-book-atlas",
refs_globally_unique=False,
),
ActivityName.SATELLITE: Activity(
name=ActivityName.SATELLITE,
comment_names=[],
description="Amateur Radio Satellite",
sig_type=ActivityType.TRADITIONAL,
icon="fa-satellite",
refs_globally_unique=False,
),
ActivityName.EME: Activity(
name=ActivityName.EME,
comment_names=[],
description="Earth-Moon-Earth (Moonbounce)",
sig_type=ActivityType.TRADITIONAL,
icon="fa-moon",
refs_globally_unique=False,
),
ActivityName.AERONAUTICAL_MOBILE: Activity(
name=ActivityName.AERONAUTICAL_MOBILE,
# Don't pick /AM out of comments, spot.py will handle picking it out of the callsign
comment_names=[],
description="Aeronautical Mobile",
sig_type=ActivityType.TRADITIONAL,
icon="fa-plane",
refs_globally_unique=False,
),
ActivityName.MARITIME_MOBILE: Activity(
name=ActivityName.MARITIME_MOBILE,
# Don't pick /MM out of comments, spot.py will handle picking it out of the callsign
comment_names=[],
description="Maritime Mobile",
sig_type=ActivityType.TRADITIONAL,
icon="fa-sailboat",
refs_globally_unique=False,
),
ActivityName.QRP: Activity(
name=ActivityName.QRP,
comment_names=["QRP"],
description="Low power",
sig_type=ActivityType.TRADITIONAL,
icon="fa-volume-low",
refs_globally_unique=False,
),
ActivityName.POTA: Activity(
name=ActivityName.POTA,
comment_names=["POTA"],
description="Parks on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.PARK,
ref_regex=r"[A-Z]{2}\-\d{4,5}|K\-TEST",
icon="fa-tree",
refs_globally_unique=False,
),
ActivityName.SOTA: Activity(
name=ActivityName.SOTA,
comment_names=["SOTA"],
description="Summits on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.SUMMIT,
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}",
icon="fa-mountain-sun",
refs_globally_unique=False,
),
ActivityName.WWFF: Activity(
name=ActivityName.WWFF,
comment_names=["WWFF"],
description="World Wide Flora & Fauna",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.PARK,
ref_regex=r"[A-Z0-9]{1,3}FF\-\d{4}",
icon="fa-seedling",
refs_globally_unique=True,
),
ActivityName.GMA: Activity(
name=ActivityName.GMA,
comment_names=["GMA"],
description="Global Mountain Activity",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.SUMMIT,
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}",
icon="fa-person-hiking",
refs_globally_unique=False,
),
ActivityName.WWBOTA: Activity(
name=ActivityName.WWBOTA,
comment_names=["WWBOTA", "BOTA"],
description="Worldwide Bunkers on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.BUNKER,
ref_regex=r"B\/[A-Z0-9]{1,3}\-\d{3,4}",
icon="fa-radiation",
refs_globally_unique=True,
),
ActivityName.HEMA: Activity(
name=ActivityName.HEMA,
comment_names=["HEMA"],
description="HuMPs Excluding Marilyns Award",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.SUMMIT,
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{3}\-\d{3}",
icon="fa-mound",
refs_globally_unique=False,
),
ActivityName.IOTA: Activity(
name=ActivityName.IOTA,
comment_names=["IOTA"],
description="Islands on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.ISLAND,
ref_regex=r"[A-Z]{2}\-\d{3}",
icon="fa-book-atlas",
refs_globally_unique=False,
),
ActivityName.GMA_ISLANDS: Activity(
name=ActivityName.GMA_ISLANDS,
comment_names=[],
description="Global Mountain Activity - Islands",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.ISLAND,
ref_regex=r"(([A-Z]{2}\-\d{3})|([A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}))",
icon="fa-person-hiking",
refs_globally_unique=False,
),
ActivityName.ARLHS: Activity(
name=ActivityName.ARLHS,
comment_names=["ARLHS"],
description="Amateur Radio Lighthouse Society",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.LIGHTHOUSE,
ref_regex=r"[A-Z]{3}[\- ]\d{3,4}",
icon="fa-house-flood-water",
refs_globally_unique=False,
),
ActivityName.ILLW: Activity(
name=ActivityName.ILLW,
comment_names=["ILLW"],
description="International Lighthouse & Lightship Weekend",
sig_type=ActivityType.EVENT,
ref_type=ActivityRefType.LIGHTHOUSE,
ref_regex=r"[A-Z]{2}\d{4}",
icon="fa-house-flood-water",
refs_globally_unique=False,
),
ActivityName.MOTA: Activity(
name=ActivityName.MOTA,
comment_names=["MOTA"],
description="Mills on the Air",
sig_type=ActivityType.EVENT,
ref_type=ActivityRefType.MILL,
ref_regex=r"X\d{4,6}",
icon="fa-fan",
refs_globally_unique=True,
),
ActivityName.SIOTA: Activity(
name=ActivityName.SIOTA,
comment_names=["SIOTA"],
description="Silos on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.SILO,
ref_regex=r"[A-Z]{2}\-[A-Z]{3}\d",
icon="fa-wheat-awn",
refs_globally_unique=False,
),
ActivityName.WCA: Activity(
name=ActivityName.WCA,
comment_names=["WCA"],
description="World Castles Award",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.CASTLE,
ref_regex=r"[A-Z0-9]{1,3}\-\d{5}",
icon="fa-chess-rook",
refs_globally_unique=False,
),
ActivityName.ZLOTA: Activity(
name=ActivityName.ZLOTA,
comment_names=["ZLOTA"],
description="New Zealand on the Air",
sig_type=ActivityType.REGIONAL,
ref_type=None,
ref_regex=r"ZL[A-Z]/[A-Z]{2}\-\d{3,4}",
icon="fa-kiwi-bird",
region_flag="🇳🇿",
refs_globally_unique=True,
),
ActivityName.WOTA: Activity(
name=ActivityName.WOTA,
comment_names=["WOTA"],
description="Wainwrights on the Air",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.SUMMIT,
ref_regex=r"[A-Z]{3}-[0-9]{2}",
icon="fa-w",
region_flag="🇬🇧",
refs_globally_unique=False,
),
ActivityName.BOTA: Activity(
name=ActivityName.BOTA,
comment_names=[],
description="Beaches on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.BEACH,
icon="fa-umbrella-beach",
refs_globally_unique=False,
),
ActivityName.KRMNPA: Activity(
name=ActivityName.KRMNPA,
comment_names=["KRMNPA"],
description="Keith Roget Memorial National Parks Award",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.PARK,
ref_regex=r"VKFF\-\d{4}",
icon="fa-earth-oceania",
region_flag="🇦🇺",
refs_globally_unique=False,
),
ActivityName.SANPCPA: Activity(
name=ActivityName.SANPCPA,
comment_names=["SANPCPA"],
description="South Australian National Parks and Conservation Parks Award",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.PARK,
ref_regex=r"VKFF\-\d{4}",
icon="fa-earth-oceania",
region_flag="🇦🇺",
refs_globally_unique=False,
),
ActivityName.LLOTA: Activity(
name=ActivityName.LLOTA,
comment_names=["LLOTA"],
description="Lagos y Lagunas on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.LAKE,
ref_regex=r"LL[A-Z]{2}\-\d{4}",
icon="fa-water",
refs_globally_unique=True,
),
ActivityName.TOWERS: Activity(
name=ActivityName.TOWERS,
comment_names=["TOTA"],
description="Towers on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.TOWER,
ref_regex=r"[A-Z]{2,3}R\-\d{4}",
icon="fa-tower-observation",
refs_globally_unique=False,
),
ActivityName.TILES: Activity(
name=ActivityName.TILES,
comment_names=[],
description="Tiles on the Air",
sig_type=ActivityType.ADVENTURE,
ref_type=ActivityRefType.GRID,
ref_regex=r"[A-Za-z]{2}[0-9]{2}[A-Za-z]{2}",
icon="fa-square",
refs_globally_unique=False,
),
ActivityName.RADAR_RALLY: Activity(
name=ActivityName.RADAR_RALLY,
comment_names=["RaDAR"],
description="RaDAR Rally",
sig_type=ActivityType.EVENT,
icon="fa-headset",
refs_globally_unique=False,
),
ActivityName.WAB: Activity(
name=ActivityName.WAB,
comment_names=["WAB"],
description="Worked All Britain",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.GRID,
ref_regex=r"[A-Z]{1,2}[0-9]{2}",
icon="fa-table-cells-large",
region_flag="🇬🇧",
refs_globally_unique=False,
),
ActivityName.WAI: Activity(
name=ActivityName.WAI,
comment_names=["WAI"],
description="Worked All Ireland",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.GRID,
ref_regex=r"[A-Z][0-9]{2}",
icon="fa-table-cells-large",
region_flag="🇮🇪",
refs_globally_unique=False,
),
ActivityName.DMF: Activity(
name=ActivityName.DMF,
comment_names=["DMF"],
description="Diplôme des Moulins de France",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.MILL,
icon="fa-fan",
region_flag="🇫🇷",
refs_globally_unique=False,
),
ActivityName.DME: Activity(
name=ActivityName.DME,
comment_names=["DME"],
description="Diploma Municipios de España",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.TOWN,
ref_regex=r"DME[\- ]\d{3,5}",
icon="fa-building",
region_flag="🇪🇸",
refs_globally_unique=True,
),
ActivityName.FEA: Activity(
name=ActivityName.FEA,
comment_names=["FEA"],
description="Diploma Faros de España",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.LIGHTHOUSE,
# FEA references are technically [DE]\-\d{4}(\.\d)? but spotters always seem to miss out the D- or E-
# prefix and just use FEA-1234 or FEA 1234, so allow for that. The FEA activity ref data provider adds both
# forms to the database.
ref_regex=r"([DE]|FEA)[\- ]\d{4}(\.\d)?",
icon="fa-house-flood-water",
region_flag="🇪🇸",
refs_globally_unique=True,
),
ActivityName.DMUE: Activity(
name=ActivityName.DMUE,
comment_names=["DMUE"],
description="Diploma Museos de España",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.BUILDING,
ref_regex=r"MUE[A-Z]{2}-\d{3}",
icon="fa-landmark",
region_flag="🇪🇸",
refs_globally_unique=True,
),
ActivityName.DMVE: Activity(
name=ActivityName.DMVE,
comment_names=["DMVE"],
description="Diploma Monumentos y Vestigios de España",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.BUILDING,
ref_regex=r"MV[A-Z]{1,2}-\d{4}",
icon="fa-monument",
region_flag="🇪🇸",
refs_globally_unique=True,
),
ActivityName.DCE: Activity(
name=ActivityName.DCE,
comment_names=["DCE"],
description="Diploma Castillos de España",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.CASTLE,
ref_regex=r"C[A-Z]{1,2}-\d{3}",
icon="fa-chess-rook",
region_flag="🇪🇸",
refs_globally_unique=False,
),
ActivityName.DEFE: Activity(
name=ActivityName.DEFE,
comment_names=["DEFE"],
description="Diploma Estaciones de Ferrocarril de España",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.BUILDING,
ref_regex=r"EF[A-Z]{1,2}-\d{3}",
icon="fa-train",
region_flag="🇪🇸",
refs_globally_unique=True,
),
ActivityName.DTMBA: Activity(
name=ActivityName.DTMBA,
comment_names=["DTMBA"],
description="Diploma Teatri Musei e Belle Arti",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.BUILDING,
ref_regex=r"I-?[0-9]{3,4}\s?[A-Z]{2}",
icon="fa-landmark",
region_flag="🇮🇹",
refs_globally_unique=True,
),
ActivityName.BIWOTA: Activity(
name=ActivityName.BIWOTA,
comment_names=["BIWOTA"],
description="British Inland Waterways on the Air",
sig_type=ActivityType.EVENT,
ref_type=ActivityRefType.WATERWAY,
icon="fa-ship",
region_flag="🇬🇧",
refs_globally_unique=False,
),
ActivityName.COTA: Activity(
name=ActivityName.COTA,
comment_names=["COTA"],
description="Castles on the Air",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.CASTLE,
ref_regex=r"[A-Z]{3}\-[0-9]{3,5}",
icon="fa-chess-rook",
region_flag="🇩🇪",
refs_globally_unique=False,
),
ActivityName.PGA: Activity(
name=ActivityName.PGA,
comment_names=["PGA"],
description="Polish Gmina Award",
sig_type=ActivityType.REGIONAL,
ref_type=ActivityRefType.REGION,
ref_regex=r"[A-Z]{2}[0-9]{2}",
icon="fa-g",
region_flag="🇵🇱",
refs_globally_unique=False,
),
ActivityName.TOILETS: Activity(
name=ActivityName.TOILETS,
comment_names=[],
description="Toilets on the Air",
sig_type=ActivityType.EVENT,
ref_type=ActivityRefType.TOILET,
ref_regex=r"T\-[0-9]{2}",
icon="fa-toilet",
region_flag="🏴‍☠️",
refs_globally_unique=True,
),
}
+2 -2
View File
@@ -1,6 +1,6 @@
from dataclasses import dataclass, field
from core.enums import ActivityRefType, ActivityType
from core.enums import ActivityName, ActivityRefType, ActivityType
@dataclass
@@ -15,7 +15,7 @@ class Activity:
match what references (such as parks and summits) look like for that programme."""
# Activity name as used in the UI and API, e.g. "Towers"
name: str
name: ActivityName
# Description, e.g. "Towers on the Air"
description: str
# Type, either Worldwide, Regional or Event. Used for sorting in the web UI.
+11 -10
View File
@@ -18,9 +18,9 @@ from core.activity_utils import (
)
from core.call_lookup_helper import get_call_info
from core.config import MAX_SPOT_AGE
from core.constants import ACTIVITIES, PROPAGATION_MODES
from core.constants import PROPAGATION_MODES
from core.data_store import DATA_STORE
from core.enums import Continent, LocationSourceForSpot, Mode, ModeSource, ModeType
from core.enums import ActivityName, Continent, LocationSourceForSpot, Mode, ModeSource, ModeType
from core.geo_utils import lat_lon_to_cq_zone, lat_lon_to_itu_zone
from core.utils import (
get_flag_for_dxcc,
@@ -29,6 +29,7 @@ from core.utils import (
infer_mode_from_frequency,
infer_mode_type_from_mode,
)
from data.activities import ACTIVITIES
from data.activity_ref import ActivityRef
logger = logging.getLogger(__name__)
@@ -311,7 +312,7 @@ class Spot:
# name, but where the activity reference is unique-looking enough that we can't confuse it with any other
# activity.
if self.comment:
for activity in ACTIVITIES:
for activity in ACTIVITIES.values():
if activity.refs_globally_unique and activity.ref_regex:
ref_matches = re.finditer(
r"(^|\W)(" + activity.ref_regex + r")($|\W)", self.comment, re.IGNORECASE
@@ -343,7 +344,7 @@ class Spot:
):
self.dx_latitude = activity_ref.latitude
self.dx_longitude = activity_ref.longitude
if self.sig == "WAB" or self.sig == "WAI" or self.sig == "Tiles":
if self.sig in (ActivityName.WAB, ActivityName.WAI, ActivityName.TILES):
self.dx_location_source = LocationSourceForSpot.GRID
else:
self.dx_location_source = LocationSourceForSpot.SIG_REF_LOOKUP
@@ -380,16 +381,16 @@ class Spot:
# Set activities based on propagation mode
if self.propagation_mode == "Satellite" and not self.sig:
self.sig = "Satellite"
self.sig = ActivityName.SATELLITE
if self.propagation_mode == "Earth-Moon-Earth" and not self.sig:
self.sig = "EME"
self.sig = ActivityName.EME
# Set activities based on the DX callsign suffix
if self.dx_call and not self.sig:
if self.dx_call.upper().endswith("/AM"):
self.sig = "/AM"
elif self.dx_call.upper().endswith("/MM"):
self.sig = "/MM"
if self.dx_call.upper().endswith(ActivityName.AERONAUTICAL_MOBILE):
self.sig = ActivityName.AERONAUTICAL_MOBILE
elif self.dx_call.upper().endswith(ActivityName.MARITIME_MOBILE):
self.sig = ActivityName.MARITIME_MOBILE
# Parse "de_grid -> dx_grid" structures from the comment
if self.comment: