feat: implement DLD (Deutschland Diplom) award
Add DOK-based award tracking with DCL confirmation. Counts unique (DOK, band, mode) combinations toward the 100 DOK target. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
13
award-definitions/dld.json
Normal file
13
award-definitions/dld.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"id": "dld",
|
||||||
|
"name": "DLD",
|
||||||
|
"description": "Deutschland Diplom - Confirm 100 unique DOKs on different bands/modes",
|
||||||
|
"caption": "Contact and confirm stations with 100 unique DOKs (DARC Ortsverband Kennung) on different band/mode combinations. Each unique DOK on a unique band/mode counts as one point. Only DCL-confirmed QSOs with valid DOK information count toward this award.",
|
||||||
|
"category": "darc",
|
||||||
|
"rules": {
|
||||||
|
"type": "dok",
|
||||||
|
"target": 100,
|
||||||
|
"confirmationType": "dcl",
|
||||||
|
"displayField": "darcDok"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ function loadAwardDefinitions() {
|
|||||||
'vucc-sat.json',
|
'vucc-sat.json',
|
||||||
'sat-rs44.json',
|
'sat-rs44.json',
|
||||||
'special-stations.json',
|
'special-stations.json',
|
||||||
|
'dld.json',
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
@@ -108,6 +109,11 @@ export async function calculateAwardProgress(userId, award, options = {}) {
|
|||||||
hasFilters: !!rules.filters,
|
hasFilters: !!rules.filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle DOK-based awards (DLD)
|
||||||
|
if (rules.type === 'dok') {
|
||||||
|
return calculateDOKAwardProgress(userId, award, { includeDetails });
|
||||||
|
}
|
||||||
|
|
||||||
// Handle point-based awards
|
// Handle point-based awards
|
||||||
if (rules.type === 'points') {
|
if (rules.type === 'points') {
|
||||||
return calculatePointsAwardProgress(userId, award, { includeDetails });
|
return calculatePointsAwardProgress(userId, award, { includeDetails });
|
||||||
@@ -156,6 +162,111 @@ export async function calculateAwardProgress(userId, award, options = {}) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate progress for DOK-based awards (DLD)
|
||||||
|
* Counts unique (DOK, band, mode) combinations with DCL confirmation
|
||||||
|
* @param {number} userId - User ID
|
||||||
|
* @param {Object} award - Award definition
|
||||||
|
* @param {Object} options - Options
|
||||||
|
* @param {boolean} options.includeDetails - Include detailed entity breakdown
|
||||||
|
*/
|
||||||
|
async function calculateDOKAwardProgress(userId, award, options = {}) {
|
||||||
|
const { includeDetails = false } = options;
|
||||||
|
const { rules } = award;
|
||||||
|
const { target, displayField } = rules;
|
||||||
|
|
||||||
|
logger.debug('Calculating DOK-based award progress', { userId, awardId: award.id, target });
|
||||||
|
|
||||||
|
// Get all QSOs for user
|
||||||
|
const allQSOs = await db
|
||||||
|
.select()
|
||||||
|
.from(qsos)
|
||||||
|
.where(eq(qsos.userId, userId));
|
||||||
|
|
||||||
|
logger.debug('Total QSOs for user', { count: allQSOs.length });
|
||||||
|
|
||||||
|
// Track unique (DOK, band, mode) combinations
|
||||||
|
const dokCombinations = new Map(); // Key: "DOK/band/mode" -> detail object
|
||||||
|
|
||||||
|
for (const qso of allQSOs) {
|
||||||
|
const dok = qso.darcDok;
|
||||||
|
if (!dok) continue; // Skip QSOs without DOK
|
||||||
|
|
||||||
|
const band = qso.band || 'Unknown';
|
||||||
|
const mode = qso.mode || 'Unknown';
|
||||||
|
const combinationKey = `${dok}/${band}/${mode}`;
|
||||||
|
|
||||||
|
// Initialize combination if not exists
|
||||||
|
if (!dokCombinations.has(combinationKey)) {
|
||||||
|
dokCombinations.set(combinationKey, {
|
||||||
|
entity: dok,
|
||||||
|
entityId: null,
|
||||||
|
entityName: dok,
|
||||||
|
band,
|
||||||
|
mode,
|
||||||
|
worked: false,
|
||||||
|
confirmed: false,
|
||||||
|
qsoDate: qso.qsoDate,
|
||||||
|
dclQslRdate: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const detail = dokCombinations.get(combinationKey);
|
||||||
|
detail.worked = true;
|
||||||
|
|
||||||
|
// Check for DCL confirmation
|
||||||
|
if (qso.dclQslRstatus === 'Y') {
|
||||||
|
if (!detail.confirmed) {
|
||||||
|
detail.confirmed = true;
|
||||||
|
detail.dclQslRdate = qso.dclQslRdate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const workedDOKs = new Set();
|
||||||
|
const confirmedDOKs = new Set();
|
||||||
|
|
||||||
|
for (const [key, detail] of dokCombinations) {
|
||||||
|
const dok = detail.entity;
|
||||||
|
workedDOKs.add(dok);
|
||||||
|
if (detail.confirmed) {
|
||||||
|
confirmedDOKs.add(dok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug('DOK award progress', {
|
||||||
|
workedDOKs: workedDOKs.size,
|
||||||
|
confirmedDOKs: confirmedDOKs.size,
|
||||||
|
target,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Base result
|
||||||
|
const result = {
|
||||||
|
worked: workedDOKs.size,
|
||||||
|
confirmed: confirmedDOKs.size,
|
||||||
|
target: target || 0,
|
||||||
|
percentage: target ? Math.round((confirmedDOKs.size / target) * 100) : 0,
|
||||||
|
workedEntities: Array.from(workedDOKs),
|
||||||
|
confirmedEntities: Array.from(confirmedDOKs),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add details if requested
|
||||||
|
if (includeDetails) {
|
||||||
|
result.award = {
|
||||||
|
id: award.id,
|
||||||
|
name: award.name,
|
||||||
|
description: award.description,
|
||||||
|
caption: award.caption,
|
||||||
|
target: target || 0,
|
||||||
|
};
|
||||||
|
result.entities = Array.from(dokCombinations.values());
|
||||||
|
result.total = result.entities.length;
|
||||||
|
result.confirmed = result.entities.filter((e) => e.confirmed).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate progress for point-based awards
|
* Calculate progress for point-based awards
|
||||||
* countMode determines how points are counted:
|
* countMode determines how points are counted:
|
||||||
@@ -510,6 +621,11 @@ export async function getAwardEntityBreakdown(userId, awardId) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle DOK-based awards - use the dedicated function
|
||||||
|
if (rules.type === 'dok') {
|
||||||
|
return await calculateDOKAwardProgress(userId, award, { includeDetails: true });
|
||||||
|
}
|
||||||
|
|
||||||
// Handle point-based awards - use the unified function
|
// Handle point-based awards - use the unified function
|
||||||
if (rules.type === 'points') {
|
if (rules.type === 'points') {
|
||||||
return await calculatePointsAwardProgress(userId, award, { includeDetails: true });
|
return await calculatePointsAwardProgress(userId, award, { includeDetails: true });
|
||||||
|
|||||||
Reference in New Issue
Block a user