From 6c9aa1efe7aa655facc086d5d21fb00ab20f26ff Mon Sep 17 00:00:00 2001 From: Joerg Date: Thu, 22 Jan 2026 08:19:32 +0100 Subject: [PATCH] feat: add allowed_bands filter to award definitions Adds a new "allowed_bands" key to award definitions that restricts which bands count toward an award. If absent, all bands are allowed (default behavior). Applied to DXCC award to only count HF bands (160m-10m), excluding VHF/UHF bands like 6m, 2m, and 70cm. Co-Authored-By: Claude --- award-definitions/dxcc.json | 7 ++++--- src/backend/services/awards.service.js | 23 +++++++++++++++++++++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/award-definitions/dxcc.json b/award-definitions/dxcc.json index a0b79f2..89e5f32 100644 --- a/award-definitions/dxcc.json +++ b/award-definitions/dxcc.json @@ -1,13 +1,14 @@ { "id": "dxcc", "name": "DXCC", - "description": "Confirm 100 DXCC entities on any band/mode", - "caption": "Contact and confirm 100 different DXCC entities. Any band and mode combination counts. QSOs are confirmed when LoTW QSL is received.", + "description": "Confirm 100 DXCC entities on HF bands", + "caption": "Contact and confirm 100 different DXCC entities on HF bands (160m-10m). Only HF band QSOs count toward this award. QSOs are confirmed when LoTW QSL is received.", "category": "dxcc", "rules": { "type": "entity", "entityType": "dxcc", "target": 100, - "displayField": "entity" + "displayField": "entity", + "allowed_bands": ["160m", "80m", "60m", "40m", "30m", "20m", "17m", "15m", "12m", "10m"] } } diff --git a/src/backend/services/awards.service.js b/src/backend/services/awards.service.js index 88dd5d7..1ea5a15 100644 --- a/src/backend/services/awards.service.js +++ b/src/backend/services/awards.service.js @@ -139,11 +139,21 @@ export async function calculateAwardProgress(userId, award, options = {}) { logger.debug('QSOs after filters', { count: filteredQSOs.length }); } + // Apply allowed_bands filter if present + let finalQSOs = filteredQSOs; + if (rules.allowed_bands && Array.isArray(rules.allowed_bands) && rules.allowed_bands.length > 0) { + finalQSOs = filteredQSOs.filter(qso => { + const band = qso.band; + return rules.allowed_bands.includes(band); + }); + logger.debug('QSOs after allowed_bands filter', { count: finalQSOs.length }); + } + // Calculate worked and confirmed entities const workedEntities = new Set(); const confirmedEntities = new Set(); - for (const qso of filteredQSOs) { + for (const qso of finalQSOs) { const entity = getEntityValue(qso, rules.entityType); if (entity) { @@ -708,11 +718,20 @@ export async function getAwardEntityBreakdown(userId, awardId) { // Apply filters const filteredQSOs = applyFilters(allQSOs, rules.filters); + // Apply allowed_bands filter if present + let finalQSOs = filteredQSOs; + if (rules.allowed_bands && Array.isArray(rules.allowed_bands) && rules.allowed_bands.length > 0) { + finalQSOs = filteredQSOs.filter(qso => { + const band = qso.band; + return rules.allowed_bands.includes(band); + }); + } + // Group by (entity, band, mode) slot for entity awards // This allows showing multiple QSOs per entity on different bands/modes const slotMap = new Map(); // Key: "entity/band/mode" -> slot object - for (const qso of filteredQSOs) { + for (const qso of finalQSOs) { const entity = getEntityValue(qso, rules.entityType); if (!entity) continue;