From ed1f9e5b0637732c611a38a56cdee2314518897d Mon Sep 17 00:00:00 2001 From: Ian Renton Date: Sun, 29 Mar 2026 08:31:36 +0100 Subject: [PATCH] Simplify API for band conditions #92 --- data/solar_conditions.py | 24 ++------ solarconditionsproviders/hamqsl.py | 23 ++++--- templates/about.html | 2 +- templates/add_spot.html | 4 +- templates/alerts.html | 4 +- templates/bands.html | 6 +- templates/base.html | 8 +-- templates/conditions.html | 16 ++--- templates/map.html | 6 +- templates/spots.html | 6 +- templates/status.html | 4 +- webassets/apidocs/openapi.yml | 99 +++++++++++++++--------------- webassets/js/conditions.js | 32 ++++------ 13 files changed, 110 insertions(+), 124 deletions(-) diff --git a/data/solar_conditions.py b/data/solar_conditions.py index 9a4710d..b422a14 100644 --- a/data/solar_conditions.py +++ b/data/solar_conditions.py @@ -95,18 +95,6 @@ class HFBandCondition: condition: str = None -@dataclass -class VHFCondition: - """Data class representing a VHF propagation condition.""" - - # Phenomenon name, e.g. "E-Skip", "Sporadic E" - phenomenon: str = None - # Geographic location this applies to, e.g. "Europe", "N America" - location: str = None - # Condition description, e.g. "Band Closed", "Enhanced", "Good" - condition: str = None - - @dataclass class SolarConditions: """Data class representing current solar and propagation conditions.""" @@ -139,13 +127,13 @@ class SolarConditions: geomag_field: str = None # Geomagnetic background noise level, e.g. "S0", "S1", "S2" geomag_noise: str = None - # HF band propagation conditions - hf_conditions: list = None # list[HFBandCondition] - # VHF propagation phenomena - vhf_conditions: list = None # list[VHFCondition] + # HF band propagation conditions, keyed by "{band}-{time}" e.g. "80m-40m-day" + hf_conditions: dict = None + # VHF propagation conditions, keyed by condition name + vhf_conditions: dict = None # Derived values (populated by infer_descriptions()) - # HF radio blackout risk, derived from x_ray + # HF radio blackout risk description, derived from x_ray blackout_desc: str = None # Solar radiation storm level description, derived from proton_flux proton_flux_desc: str = None @@ -157,7 +145,7 @@ class SolarConditions: geomag_storm_scale: int = None # Overall HF band conditions summary, derived from sfi band_conditions_desc: str = None - # Electron flux level, derived from electron_flux + # Electron flux description, derived from electron_flux electron_flux_desc: str = None def infer_descriptions(self): diff --git a/solarconditionsproviders/hamqsl.py b/solarconditionsproviders/hamqsl.py index 8de8648..6f98bd2 100644 --- a/solarconditionsproviders/hamqsl.py +++ b/solarconditionsproviders/hamqsl.py @@ -4,7 +4,7 @@ from xml.etree import ElementTree import pytz from dateutil import parser as dateutil_parser, tz as dateutil_tz -from data.solar_conditions import HFBandCondition, VHFCondition + from solarconditionsproviders.http_solar_conditions_provider import HTTPSolarConditionsProvider POLL_INTERVAL = 3600 # 1 hour @@ -48,7 +48,7 @@ class HamQSL(HTTPSolarConditionsProvider): return default # Process HF band conditions - hf_conditions = [] + hf_conditions = {} calc = sd.find("calculatedconditions") if calc is not None: for band_el in calc.findall("band"): @@ -56,18 +56,15 @@ class HamQSL(HTTPSolarConditionsProvider): time = band_el.get("time") condition = band_el.text.strip() if band_el.text else None if name and time and condition: - hf_conditions.append(HFBandCondition(band=name, time=time, condition=condition)) + hf_conditions[f"{name}-{time}"] = condition # Process VHF propagation conditions - vhf_conditions = [] + vhf_map = {} vhf = sd.find("calculatedvhfconditions") if vhf is not None: for ph_el in vhf.findall("phenomenon"): - vhf_conditions.append(VHFCondition( - phenomenon=ph_el.get("name"), - location=ph_el.get("location"), - condition=ph_el.text.strip() if ph_el.text else None - )) + key = (ph_el.get("name"), ph_el.get("location")) + vhf_map[key] = ph_el.text.strip() if ph_el.text else None # Parse the "updated" timestamp string (format: "28 Mar 2026 0949 GMT") to UTC epoch seconds. updated = None @@ -100,5 +97,11 @@ class HamQSL(HTTPSolarConditionsProvider): "geomag_field": (lambda v: "Unsettled" if v == "Unsettld" else v)(text("geomagfield").title()) if text("geomagfield") else None, "geomag_noise": text("signalnoise"), "hf_conditions": hf_conditions, - "vhf_conditions": vhf_conditions + "vhf_conditions": { + "vhf_aurora_northern_hemi": vhf_map.get(("vhf-aurora", "northern_hemi")), + "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")), + "es_2m_na": vhf_map.get(("E-Skip", "north_america")), + }, } diff --git a/templates/about.html b/templates/about.html index 846e630..f02d61d 100644 --- a/templates/about.html +++ b/templates/about.html @@ -67,7 +67,7 @@

This software is dedicated to the memory of Tom G1PJB, SK, a friend and colleague who sadly passed away around the time I started writing it in Autumn 2025. I was looking forward to showing it to you when it was done.

- + {% end %} \ No newline at end of file diff --git a/templates/add_spot.html b/templates/add_spot.html index 36549f5..f87c35a 100644 --- a/templates/add_spot.html +++ b/templates/add_spot.html @@ -69,8 +69,8 @@ - - + + {% end %} \ No newline at end of file diff --git a/templates/alerts.html b/templates/alerts.html index 9e96b7b..504d549 100644 --- a/templates/alerts.html +++ b/templates/alerts.html @@ -56,8 +56,8 @@ - - + + {% end %} \ No newline at end of file diff --git a/templates/bands.html b/templates/bands.html index 4804829..d77f2c3 100644 --- a/templates/bands.html +++ b/templates/bands.html @@ -62,9 +62,9 @@ - - - + + + {% end %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 7532ae3..9729d50 100644 --- a/templates/base.html +++ b/templates/base.html @@ -46,10 +46,10 @@ crossorigin="anonymous"> - - - - + + + + diff --git a/templates/conditions.html b/templates/conditions.html index a2ed256..de1cde2 100644 --- a/templates/conditions.html +++ b/templates/conditions.html @@ -59,23 +59,23 @@ Sporadic-E 6m (Europe) - + Sporadic-E 4m (Europe) - + Sporadic-E 2m (Europe) - + Sporadic-E 2m (North America) - + Aurora (Northern Hemisphere) - + Aurora Minimum Latitude @@ -87,7 +87,7 @@ -
Data from HamQSL.net.
+
Data from HamQSL.net.
@@ -100,8 +100,8 @@ - - + + {% end %} \ No newline at end of file diff --git a/templates/map.html b/templates/map.html index 2ba97a6..710a551 100644 --- a/templates/map.html +++ b/templates/map.html @@ -70,9 +70,9 @@ - - - + + + {% end %} \ No newline at end of file diff --git a/templates/spots.html b/templates/spots.html index d78f9d6..86d61dc 100644 --- a/templates/spots.html +++ b/templates/spots.html @@ -87,9 +87,9 @@ - - - + + + {% end %} \ No newline at end of file diff --git a/templates/status.html b/templates/status.html index 0739cee..3e5feac 100644 --- a/templates/status.html +++ b/templates/status.html @@ -59,8 +59,8 @@ - - + + diff --git a/webassets/apidocs/openapi.yml b/webassets/apidocs/openapi.yml index 9b9d516..d6382ed 100644 --- a/webassets/apidocs/openapi.yml +++ b/webassets/apidocs/openapi.yml @@ -1356,47 +1356,6 @@ components: description: Regex that matches this SIG's reference IDs. Generally for Spothole's own internal use, clients probably won't need this. example: "[A-Z]{2}\\-\\d+" - HFBandCondition: - type: object - description: HF propagation conditions for a group of bands at a particular time of day. - properties: - band: - type: string - description: Band group, e.g. "80m-40m", "30m-20m", "17m-15m", "10m-6m". As provided by HamQSL. - example: "80m-40m" - time: - type: string - description: Time of day these conditions apply to. As provided by HamQSL. - enum: - - day - - night - example: day - condition: - type: string - description: Propagation condition assessment. As provided by HamQSL. - enum: - - Good - - Fair - - Poor - example: Good - - VHFCondition: - type: object - description: A VHF propagation phenomenon and its current condition. - properties: - phenomenon: - type: string - description: The name of the propagation phenomenon, e.g. "E-Skip", "vhf-aurora". As provided by HamQSL. - example: "E-Skip" - location: - type: string - description: The geographic region this condition applies to, e.g. "europe", "north_america", "northern_hemi". As provided by HamQSL. - example: "europe" - condition: - type: string - description: The current condition for this phenomenon and location. - example: "Band Closed" - SolarConditions: type: object description: Current solar and propagation conditions. All fields may be null if no provider has successfully fetched data yet. @@ -1458,15 +1417,57 @@ components: description: Geomagnetic background noise level on HF, in S-units example: "S0" hf_conditions: - type: array - description: HF propagation condition assessments by band group and time of day - items: - $ref: '#/components/schemas/HFBandCondition' + type: object + description: HF propagation condition assessments, keyed by "{band}-{time}" e.g. "80m-40m-day" + properties: + 80m-40m-day: + type: string + enum: [Good, Fair, Poor] + 80m-40m-night: + type: string + enum: [Good, Fair, Poor] + 30m-20m-day: + type: string + enum: [Good, Fair, Poor] + 30m-20m-night: + type: string + enum: [Good, Fair, Poor] + 17m-15m-day: + type: string + enum: [Good, Fair, Poor] + 17m-15m-night: + type: string + enum: [Good, Fair, Poor] + 12m-10m-day: + type: string + enum: [Good, Fair, Poor] + 12m-10m-night: + type: string + enum: [Good, Fair, Poor] vhf_conditions: - type: array - description: VHF propagation condition assessments by phenomenon and location - items: - $ref: '#/components/schemas/VHFCondition' + type: object + description: VHF propagation condition assessments, keyed by condition name + properties: + vhf_aurora_northern_hemi: + type: string + description: VHF aurora propagation condition for the northern hemisphere + example: "Band Closed" + es_2m_europe: + type: string + description: Sporadic-E propagation condition on 2m for Europe + example: "Band Closed" + es_4m_europe: + type: string + description: Sporadic-E propagation condition on 4m for Europe + example: "Band Closed" + es_6m_europe: + type: string + description: Sporadic-E propagation condition on 6m for Europe + example: "Band Closed" + es_2m_na: + type: string + description: Sporadic-E propagation condition on 2m for North America + example: "Band Closed" blackout_desc: type: string description: HF radio blackout risk description, derived from the X-ray flux class. diff --git a/webassets/js/conditions.js b/webassets/js/conditions.js index 12a9538..ac61974 100644 --- a/webassets/js/conditions.js +++ b/webassets/js/conditions.js @@ -1,32 +1,26 @@ // Load solar conditions function loadSolarConditions() { $.getJSON('/api/v1/solar', function(jsonData) { + + // HF + const hfConditionClass = { 'Good': 'table-success', 'Fair': 'table-warning', 'Poor': 'table-danger' }; if (jsonData.hf_conditions) { - jsonData.hf_conditions.forEach(function(entry) { - const cell = $('#hf-conditions-' + entry.band + '-' + entry.time); - cell.text(entry.condition); - const cls = hfConditionClass[entry.condition]; - if (cls) { cell.addClass(cls); } + Object.entries(jsonData.hf_conditions).forEach(function([key, condition]) { + const cell = $('#hf-conditions-' + key); + cell.text(condition); + cell.addClass(hfConditionClass[condition]); }); } - const vhfIdMap = { - 'vhf-aurora|northern_hemi': 'vhf-conditions-aurora', - 'E-Skip|europe_6m': 'vhf-conditions-es-6m-eu', - 'E-Skip|europe_4m': 'vhf-conditions-es-4m-eu', - 'E-Skip|europe': 'vhf-conditions-es-2m-eu', - 'E-Skip|north_america':'vhf-conditions-es-2m-na', - }; + // VHF + if (jsonData.vhf_conditions) { - jsonData.vhf_conditions.forEach(function(entry) { - const id = vhfIdMap[entry.phenomenon + '|' + entry.location]; - if (id) { - const cell = $('#' + id); - cell.text(entry.condition); - cell.addClass(entry.condition === 'Band Closed' ? 'table-danger' : 'table-success'); - } + Object.entries(jsonData.vhf_conditions).forEach(function([key, condition]) { + const cell = $('#vhf-conditions-' + key); + cell.text(condition); + cell.addClass(condition === 'Band Closed' ? 'table-danger' : 'table-success'); }); } if (jsonData.aurora_latitude !== null && jsonData.aurora_latitude !== undefined) {