feat: skip QSOs with unchanged confirmation data

When syncing from LoTW/DCL, only update QSOs if confirmation data
has changed. This avoids unnecessary database updates and makes it
clear which QSOs actually changed.

- LoTW: Checks if lotwQslRstatus or lotwQslRdate changed
- DCL: Checks if dclQslRstatus, dclQslRdate, DOK, or grid changed
- Frontend: Shows skipped count in sync summary

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-17 19:49:56 +01:00
parent 3592dbb4fb
commit e09ab94e63
3 changed files with 74 additions and 38 deletions

View File

@@ -192,6 +192,7 @@ export async function syncQSOs(userId, dclApiKey, sinceDate = null, jobId = null
let addedCount = 0; let addedCount = 0;
let updatedCount = 0; let updatedCount = 0;
let skippedCount = 0;
const errors = []; const errors = [];
const addedQSOs = []; const addedQSOs = [];
const updatedQSOs = []; const updatedQSOs = [];
@@ -219,26 +220,41 @@ export async function syncQSOs(userId, dclApiKey, sinceDate = null, jobId = null
.limit(1); .limit(1);
if (existing.length > 0) { if (existing.length > 0) {
// Update existing QSO with DCL confirmation and DOK data const existingQSO = existing[0];
await db
.update(qsos) // Check if DCL confirmation or DOK data has changed
.set({ const dataChanged =
dclQslRdate: dbQSO.dclQslRdate, existingQSO.dclQslRstatus !== dbQSO.dclQslRstatus ||
dclQslRstatus: dbQSO.dclQslRstatus, existingQSO.dclQslRdate !== dbQSO.dclQslRdate ||
darcDok: dbQSO.darcDok || existing[0].darcDok, existingQSO.darcDok !== (dbQSO.darcDok || existingQSO.darcDok) ||
myDarcDok: dbQSO.myDarcDok || existing[0].myDarcDok, existingQSO.myDarcDok !== (dbQSO.myDarcDok || existingQSO.myDarcDok) ||
grid: dbQSO.grid || existing[0].grid, existingQSO.grid !== (dbQSO.grid || existingQSO.grid);
gridSource: dbQSO.gridSource || existing[0].gridSource,
}) if (dataChanged) {
.where(eq(qsos.id, existing[0].id)); // Update existing QSO with changed DCL confirmation and DOK data
updatedCount++; await db
// Track updated QSO (CALL and DATE) .update(qsos)
updatedQSOs.push({ .set({
callsign: dbQSO.callsign, dclQslRdate: dbQSO.dclQslRdate,
date: dbQSO.qsoDate, dclQslRstatus: dbQSO.dclQslRstatus,
band: dbQSO.band, darcDok: dbQSO.darcDok || existingQSO.darcDok,
mode: dbQSO.mode, myDarcDok: dbQSO.myDarcDok || existingQSO.myDarcDok,
}); grid: dbQSO.grid || existingQSO.grid,
gridSource: dbQSO.gridSource || existingQSO.gridSource,
})
.where(eq(qsos.id, existingQSO.id));
updatedCount++;
// Track updated QSO (CALL and DATE)
updatedQSOs.push({
callsign: dbQSO.callsign,
date: dbQSO.qsoDate,
band: dbQSO.band,
mode: dbQSO.mode,
});
} else {
// Skip - same data
skippedCount++;
}
} else { } else {
// Insert new QSO // Insert new QSO
await db.insert(qsos).values(dbQSO); await db.insert(qsos).values(dbQSO);
@@ -274,6 +290,7 @@ export async function syncQSOs(userId, dclApiKey, sinceDate = null, jobId = null
total: adifQSOs.length, total: adifQSOs.length,
added: addedCount, added: addedCount,
updated: updatedCount, updated: updatedCount,
skipped: skippedCount,
addedQSOs, addedQSOs,
updatedQSOs, updatedQSOs,
confirmed: adifQSOs.filter(q => q.dcl_qsl_rcvd === 'Y').length, confirmed: adifQSOs.filter(q => q.dcl_qsl_rcvd === 'Y').length,

View File

@@ -222,6 +222,7 @@ export async function syncQSOs(userId, lotwUsername, lotwPassword, sinceDate = n
let addedCount = 0; let addedCount = 0;
let updatedCount = 0; let updatedCount = 0;
let skippedCount = 0;
const errors = []; const errors = [];
const addedQSOs = []; const addedQSOs = [];
const updatedQSOs = []; const updatedQSOs = [];
@@ -247,22 +248,34 @@ export async function syncQSOs(userId, lotwUsername, lotwPassword, sinceDate = n
.limit(1); .limit(1);
if (existing.length > 0) { if (existing.length > 0) {
await db const existingQSO = existing[0];
.update(qsos)
.set({ // Check if LoTW confirmation data has changed
lotwQslRdate: dbQSO.lotwQslRdate, const confirmationChanged =
lotwQslRstatus: dbQSO.lotwQslRstatus, existingQSO.lotwQslRstatus !== dbQSO.lotwQslRstatus ||
lotwSyncedAt: dbQSO.lotwSyncedAt, existingQSO.lotwQslRdate !== dbQSO.lotwQslRdate;
})
.where(eq(qsos.id, existing[0].id)); if (confirmationChanged) {
updatedCount++; await db
// Track updated QSO (CALL and DATE) .update(qsos)
updatedQSOs.push({ .set({
callsign: dbQSO.callsign, lotwQslRdate: dbQSO.lotwQslRdate,
date: dbQSO.qsoDate, lotwQslRstatus: dbQSO.lotwQslRstatus,
band: dbQSO.band, lotwSyncedAt: dbQSO.lotwSyncedAt,
mode: dbQSO.mode, })
}); .where(eq(qsos.id, existingQSO.id));
updatedCount++;
// Track updated QSO (CALL and DATE)
updatedQSOs.push({
callsign: dbQSO.callsign,
date: dbQSO.qsoDate,
band: dbQSO.band,
mode: dbQSO.mode,
});
} else {
// Skip - same data
skippedCount++;
}
} else { } else {
await db.insert(qsos).values(dbQSO); await db.insert(qsos).values(dbQSO);
addedCount++; addedCount++;
@@ -288,13 +301,14 @@ export async function syncQSOs(userId, lotwUsername, lotwPassword, sinceDate = n
} }
} }
logger.info('LoTW sync completed', { total: adifQSOs.length, added: addedCount, updated: updatedCount, jobId }); logger.info('LoTW sync completed', { total: adifQSOs.length, added: addedCount, updated: updatedCount, skipped: skippedCount, jobId });
return { return {
success: true, success: true,
total: adifQSOs.length, total: adifQSOs.length,
added: addedCount, added: addedCount,
updated: updatedCount, updated: updatedCount,
skipped: skippedCount,
addedQSOs, addedQSOs,
updatedQSOs, updatedQSOs,
errors: errors.length > 0 ? errors : undefined, errors: errors.length > 0 ? errors : undefined,

View File

@@ -296,7 +296,12 @@
<div class="alert {syncResult.success ? 'alert-success' : 'alert-error'}"> <div class="alert {syncResult.success ? 'alert-success' : 'alert-error'}">
{#if syncResult.success} {#if syncResult.success}
<h3>Sync Complete!</h3> <h3>Sync Complete!</h3>
<p>Total: {syncResult.total}, Added: {syncResult.added}, Updated: {syncResult.updated}</p> <p>
Total: {syncResult.total},
Added: {syncResult.added},
Updated: {syncResult.updated || 0},
Skipped: {syncResult.skipped || 0}
</p>
{#if syncResult.errors && syncResult.errors.length > 0} {#if syncResult.errors && syncResult.errors.length > 0}
<p class="text-small">Some QSOs had errors</p> <p class="text-small">Some QSOs had errors</p>
{/if} {/if}