Split out event type SIGs, and do other enum conversions. Closes #138

This commit is contained in:
Ian Renton
2026-09-02 09:38:05 +01:00
parent 606cf1be68
commit 9d117a6069
54 changed files with 363 additions and 155 deletions
+41 -44
View File
@@ -1,4 +1,5 @@
from core.config import SERVER_OWNER_CALLSIGN
from core.enums import SIGType
from data.band import Band
from data.sig import SIG
@@ -15,6 +16,7 @@ SIGS = [
name="POTA",
comment_names=["POTA"],
description="Parks on the Air",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z]{2}\-\d{4,5}|K\-TEST",
icon="fa-tree",
refs_globally_unique=False,
@@ -23,6 +25,7 @@ SIGS = [
name="SOTA",
comment_names=["SOTA"],
description="Summits on the Air",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}",
icon="fa-mountain-sun",
refs_globally_unique=False,
@@ -31,6 +34,7 @@ SIGS = [
name="WWFF",
comment_names=["WWFF"],
description="World Wide Flora & Fauna",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z0-9]{1,3}FF\-\d{4}",
icon="fa-seedling",
refs_globally_unique=True,
@@ -39,6 +43,7 @@ SIGS = [
name="GMA",
comment_names=["GMA"],
description="Global Mountain Activity",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}",
icon="fa-person-hiking",
refs_globally_unique=False,
@@ -47,6 +52,7 @@ SIGS = [
name="WWBOTA",
comment_names=["WWBOTA", "BOTA"],
description="Worldwide Bunkers on the Air",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"B\/[A-Z0-9]{1,3}\-\d{3,4}",
icon="fa-radiation",
refs_globally_unique=True,
@@ -55,6 +61,7 @@ SIGS = [
name="HEMA",
comment_names=["HEMA"],
description="HuMPs Excluding Marilyns Award",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{3}\-\d{3}",
icon="fa-mound",
refs_globally_unique=False,
@@ -63,6 +70,7 @@ SIGS = [
name="IOTA",
comment_names=["IOTA"],
description="Islands on the Air",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z]{2}\-\d{3}",
icon="fa-book-atlas",
refs_globally_unique=False,
@@ -71,6 +79,7 @@ SIGS = [
name="GMA Islands",
comment_names=[],
description="Global Mountain Activity - Islands",
sig_type=SIGType.WORLDWIDE,
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,
@@ -79,6 +88,7 @@ SIGS = [
name="ARLHS",
comment_names=["ARLHS"],
description="Amateur Radio Lighthouse Society",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z]{3}[\- ]\d{3,4}",
icon="fa-house-flood-water",
refs_globally_unique=False,
@@ -87,6 +97,7 @@ SIGS = [
name="ILLW",
comment_names=["ILLW"],
description="International Lighthouse & Lightship Weekend",
sig_type=SIGType.EVENT,
ref_regex=r"[A-Z]{2}\d{4}",
icon="fa-house-flood-water",
refs_globally_unique=False,
@@ -95,6 +106,7 @@ SIGS = [
name="MOTA",
comment_names=["MOTA"],
description="Mills on the Air",
sig_type=SIGType.EVENT,
ref_regex=r"X\d{4,6}",
icon="fa-fan",
refs_globally_unique=True,
@@ -103,6 +115,7 @@ SIGS = [
name="SIOTA",
comment_names=["SIOTA"],
description="Silos on the Air",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z]{2}\-[A-Z]{3}\d",
icon="fa-wheat-awn",
refs_globally_unique=False,
@@ -111,6 +124,7 @@ SIGS = [
name="WCA",
comment_names=["WCA"],
description="World Castles Award",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z0-9]{1,3}\-\d{5}",
icon="fa-chess-rook",
refs_globally_unique=False,
@@ -119,6 +133,7 @@ SIGS = [
name="ZLOTA",
comment_names=["ZLOTA"],
description="New Zealand on the Air",
sig_type=SIGType.REGIONAL,
ref_regex=r"ZL[A-Z]/[A-Z]{2}\-\d{3,4}",
icon="fa-kiwi-bird",
region_flag="🇳🇿",
@@ -128,32 +143,45 @@ SIGS = [
name="WOTA",
comment_names=["WOTA"],
description="Wainwrights on the Air",
sig_type=SIGType.REGIONAL,
ref_regex=r"[A-Z]{3}-[0-9]{2}",
icon="fa-w",
region_flag="🇬🇧",
refs_globally_unique=False,
),
SIG(name="BOTA", comment_names=[], description="Beaches on the Air", icon="fa-umbrella-beach"),
SIG(
name="BOTA",
comment_names=[],
description="Beaches on the Air",
sig_type=SIGType.WORLDWIDE,
icon="fa-umbrella-beach",
refs_globally_unique=False,
),
SIG(
name="KRMNPA",
comment_names=["KRMNPA"],
description="Keith Roget Memorial National Parks Award",
sig_type=SIGType.REGIONAL,
ref_regex=r"VKFF\-\d{4}",
icon="fa-earth-oceania",
region_flag="🇦🇺",
refs_globally_unique=False,
),
SIG(
name="SANPCPA",
comment_names=["SANPCPA"],
description="South Australian National Parks and Conservation Parks Award",
sig_type=SIGType.REGIONAL,
ref_regex=r"VKFF\-\d{4}",
icon="fa-earth-oceania",
region_flag="🇦🇺",
refs_globally_unique=False,
),
SIG(
name="LLOTA",
comment_names=["LLOTA"],
description="Lagos y Lagunas on the Air",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"LL[A-Z]{2}\-\d{4}",
icon="fa-water",
refs_globally_unique=True,
@@ -162,6 +190,7 @@ SIGS = [
name="Towers",
comment_names=["TOTA"],
description="Towers on the Air",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z]{2,3}R\-\d{4}",
icon="fa-tower-observation",
refs_globally_unique=False,
@@ -170,6 +199,7 @@ SIGS = [
name="Tiles",
comment_names=[],
description="Tiles on the Air",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Za-z]{2}[0-9]{2}[A-Za-z]{2}",
icon="fa-square",
refs_globally_unique=False,
@@ -178,6 +208,7 @@ SIGS = [
name="WAB",
comment_names=["WAB"],
description="Worked All Britain",
sig_type=SIGType.REGIONAL,
ref_regex=r"[A-Z]{1,2}[0-9]{2}",
icon="fa-table-cells-large",
region_flag="🇬🇧",
@@ -187,6 +218,7 @@ SIGS = [
name="WAI",
comment_names=["WAI"],
description="Worked All Ireland",
sig_type=SIGType.REGIONAL,
ref_regex=r"[A-Z][0-9]{2}",
icon="fa-table-cells-large",
region_flag="🇮🇪",
@@ -196,6 +228,7 @@ SIGS = [
name="DME",
comment_names=["DME"],
description="Diploma Municipios de España",
sig_type=SIGType.REGIONAL,
ref_regex=r"\d{4,5}",
icon="fa-building",
region_flag="🇪🇸",
@@ -205,6 +238,7 @@ SIGS = [
name="FEA",
comment_names=["FEA"],
description="Diploma Faros de España",
sig_type=SIGType.REGIONAL,
# 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 sigref data provider adds both
# forms to the database.
@@ -217,6 +251,7 @@ SIGS = [
name="DTMBA",
comment_names=["DTMBA"],
description="Diploma Teatri Musei e Belle Arti",
sig_type=SIGType.REGIONAL,
ref_regex=r"I-?[0-9]{3,4}\s?[A-Z]{2}",
icon="fa-masks-theater",
region_flag="🇮🇹",
@@ -226,13 +261,16 @@ SIGS = [
name="BIWOTA",
comment_names=["BIWOTA"],
description="British Inland Waterways on the Air",
sig_type=SIGType.EVENT,
icon="fa-ship",
region_flag="🇬🇧",
refs_globally_unique=False,
),
SIG(
name="COTA",
comment_names=["COTA"],
description="Castles on the Air",
sig_type=SIGType.REGIONAL,
ref_regex=r"[A-Z]{3}\-[0-9]{3,5}",
icon="fa-chess-rook",
region_flag="🇩🇪",
@@ -242,6 +280,7 @@ SIGS = [
name="PGA",
comment_names=["PGA"],
description="Polish Gmina Award",
sig_type=SIGType.REGIONAL,
ref_regex=r"[A-Z]{2}[0-9]{2}",
icon="fa-g",
region_flag="🇵🇱",
@@ -251,6 +290,7 @@ SIGS = [
name="Toilets",
comment_names=[],
description="Toilets on the Air",
sig_type=SIGType.EVENT,
ref_regex=r"T\-[0-9]{2}",
icon="fa-toilet",
region_flag="🏴‍☠️",
@@ -258,49 +298,6 @@ SIGS = [
),
]
# Modes. Note "DIGI" and "DIGITAL" are also supported but are normalised into "DATA".
CW_MODES = ["CW"]
PHONE_MODES = ["PHONE", "SSB", "AM", "FM", "DV"]
DATA_MODES = [
"DATA",
"FT8",
"FT4",
"RTTY",
"SSTV",
"JS8",
"HELL",
"PSK",
"FSK",
"OLIVIA",
"PKT",
"MSK144",
]
ALL_MODES = CW_MODES + PHONE_MODES + DATA_MODES
# Mode aliases. Sometimes we get spots with a mode described in a different way that is effectively the same as a mode
# we already know, or we want to normalise things for consistency. The lookup table for this is here. Incoming spots
# that match a key in this table will be converted to the corresponding value, so only the modes above will actually be
# present in the spots.
MODE_ALIASES = {
"USB": "SSB",
"LSB": "SSB",
"DIGITALVOICE": "DV",
"DIGITALVOI": "DV",
"DMR": "DV",
"DSTAR": "DV",
"C4FM": "DV",
"FUSION": "DV",
"M17": "DV",
"RTT": "RTTY",
"BPSK": "PSK",
"PSK31": "PSK",
"BPSK31": "PSK",
"MFSK": "FSK",
"MFSK32": "FSK",
"DIGI": "DATA",
"DIGITAL": "DATA",
}
# Band definitions
BANDS = [
Band(name="2200m", start_freq=135700, end_freq=137800),
+102
View File
@@ -11,6 +11,52 @@ class Continent(str, Enum):
AN = "AN"
class Mode(str, Enum):
CW = "CW"
PHONE = "PHONE"
SSB = "SSB"
AM = "AM"
FM = "FM"
DV = "DV"
DATA = "DATA"
FT8 = "FT8"
FT4 = "FT4"
RTTY = "RTTY"
SSTV = "SSTV"
JS8 = "JS8"
HELL = "HELL"
PSK = "PSK"
FSK = "FSK"
OLIVIA = "OLIVIA"
PKT = "PKT"
MSK144 = "MSK144"
UNKNOWN = "UNKNOWN"
@property
def is_cw(self) -> bool:
return self == Mode.CW
@property
def is_phone(self) -> bool:
return self in {Mode.PHONE, Mode.SSB, Mode.AM, Mode.FM, Mode.DV}
@property
def is_data(self) -> bool:
return not (self.is_cw or self.is_phone or self == Mode.UNKNOWN)
@staticmethod
def from_name(name):
"""Convert a string to an enum mode using the alias table."""
try:
return Mode(name.upper())
except ValueError:
try:
return Mode(MODE_ALIASES[name.upper()])
except ValueError:
return Mode.UNKNOWN
class ModeType(str, Enum):
PHONE = "PHONE"
CW = "CW"
@@ -44,3 +90,59 @@ class LocationSourceForCallsign(str, Enum):
HOME_QTH = "HOME QTH"
DXCC = "DXCC"
NONE = "NONE"
class SIGRefType(str, Enum):
"""Type of a Special Interest Group reference."""
PARK = "PARK"
SUMMIT = "SUMMIT"
CASTLE = "CASTLE"
LIGHTHOUSE = "LIGHTHOUSE"
ISLAND = "ISLAND"
BUNKER = "BUNKER"
MILL = "MILL"
SILO = "SILO"
BEACH = "BEACH"
LAKE = "LAKE"
TOWER = "TOWER"
WATERWAY = "WATERWAY"
TOWN = "TOWN"
BUILDING = "BUILDING"
REGION = "REGION"
GRID = "GRID"
TOILET = "TOILET"
UNKNOWN = "UNKNOWN"
class SIGType(str, Enum):
"""Type of a Special Interest Group. Used to group them in the web UI."""
WORLDWIDE = "WORLDWIDE"
REGIONAL = "REGIONAL"
EVENT = "EVENT"
# Mode aliases. Sometimes we get spots with a mode described in a different way that is effectively the same as a mode
# we already know, or we want to normalise things for consistency. The lookup table for this is here. Incoming spots
# that match a key in this table will be converted to the corresponding value, so only the modes above will actually be
# present in the spots.
MODE_ALIASES = {
"USB": "SSB",
"LSB": "SSB",
"DIGITALVOICE": "DV",
"DIGITALVOI": "DV",
"DMR": "DV",
"DSTAR": "DV",
"C4FM": "DV",
"FUSION": "DV",
"M17": "DV",
"RTT": "RTTY",
"BPSK": "PSK",
"PSK31": "PSK",
"BPSK31": "PSK",
"MFSK": "FSK",
"MFSK32": "FSK",
"DIGI": "DATA",
"DIGITAL": "DATA",
}
+26 -25
View File
@@ -5,17 +5,9 @@ import simplejson
from pyhamtools.frequency import freq_to_band
from pyhamtools.locator import latlong_to_locator
from core.constants import (
ALL_MODES,
BANDS,
CW_MODES,
DATA_MODES,
MODE_ALIASES,
PHONE_MODES,
UNKNOWN_BAND,
)
from core.constants import BANDS, UNKNOWN_BAND
from core.data_store import DATA_STORE
from core.enums import Continent, ModeType
from core.enums import MODE_ALIASES, Continent, Mode, ModeType
from data.callsign import Callsign, LocationSourceForCallsign
logger = logging.getLogger(__name__)
@@ -28,31 +20,40 @@ def safe_json_dumps(obj):
return simplejson.dumps(obj, ensure_ascii=False, ignore_nan=True, default=lambda o: o.__dict__)
def infer_mode_from_comment(comment):
def infer_mode_from_comment(comment: str) -> Mode:
"""Infer a mode from the comment"""
for mode in ALL_MODES:
if not comment:
return Mode.UNKNOWN
for mode in Mode:
if re.search(r"(^|\W)" + mode + r"($|\W)", comment, re.IGNORECASE):
return mode
for mode in MODE_ALIASES:
if re.search(r"(^|\W)" + mode + r"($|\W)", comment, re.IGNORECASE):
return MODE_ALIASES[mode]
return None
for alias in MODE_ALIASES:
if re.search(r"(^|\W)" + alias + r"($|\W)", comment, re.IGNORECASE):
return Mode(MODE_ALIASES[alias])
return Mode.UNKNOWN
def infer_mode_type_from_mode(mode):
"""Infer a "mode family" from a mode."""
def infer_mode_type_from_mode(mode: str) -> ModeType:
"""Infer a "mode family" from a mode ."""
if mode.upper() in CW_MODES:
return ModeType.CW
elif mode.upper() in PHONE_MODES:
return ModeType.PHONE
elif mode.upper() in DATA_MODES:
if not mode:
return ModeType.UNKNOWN
try:
mode = Mode(mode.upper())
if mode.is_cw:
return ModeType.CW
if mode.is_phone:
return ModeType.PHONE
return ModeType.DATA
else:
except ValueError:
if mode.upper() != "OTHER" and mode != "?":
logger.warning(f"Found an unrecognised mode: {mode}. Developer should categorise this.")
return None
return ModeType.UNKNOWN
def infer_band_from_freq(freq):