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,18 +220,29 @@ 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];
// Check if DCL confirmation or DOK data has changed
const dataChanged =
existingQSO.dclQslRstatus !== dbQSO.dclQslRstatus ||
existingQSO.dclQslRdate !== dbQSO.dclQslRdate ||
existingQSO.darcDok !== (dbQSO.darcDok || existingQSO.darcDok) ||
existingQSO.myDarcDok !== (dbQSO.myDarcDok || existingQSO.myDarcDok) ||
existingQSO.grid !== (dbQSO.grid || existingQSO.grid);
if (dataChanged) {
// Update existing QSO with changed DCL confirmation and DOK data
await db await db
.update(qsos) .update(qsos)
.set({ .set({
dclQslRdate: dbQSO.dclQslRdate, dclQslRdate: dbQSO.dclQslRdate,
dclQslRstatus: dbQSO.dclQslRstatus, dclQslRstatus: dbQSO.dclQslRstatus,
darcDok: dbQSO.darcDok || existing[0].darcDok, darcDok: dbQSO.darcDok || existingQSO.darcDok,
myDarcDok: dbQSO.myDarcDok || existing[0].myDarcDok, myDarcDok: dbQSO.myDarcDok || existingQSO.myDarcDok,
grid: dbQSO.grid || existing[0].grid, grid: dbQSO.grid || existingQSO.grid,
gridSource: dbQSO.gridSource || existing[0].gridSource, gridSource: dbQSO.gridSource || existingQSO.gridSource,
}) })
.where(eq(qsos.id, existing[0].id)); .where(eq(qsos.id, existingQSO.id));
updatedCount++; updatedCount++;
// Track updated QSO (CALL and DATE) // Track updated QSO (CALL and DATE)
updatedQSOs.push({ updatedQSOs.push({
@@ -239,6 +251,10 @@ export async function syncQSOs(userId, dclApiKey, sinceDate = null, jobId = null
band: dbQSO.band, band: dbQSO.band,
mode: dbQSO.mode, 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,6 +248,14 @@ export async function syncQSOs(userId, lotwUsername, lotwPassword, sinceDate = n
.limit(1); .limit(1);
if (existing.length > 0) { if (existing.length > 0) {
const existingQSO = existing[0];
// Check if LoTW confirmation data has changed
const confirmationChanged =
existingQSO.lotwQslRstatus !== dbQSO.lotwQslRstatus ||
existingQSO.lotwQslRdate !== dbQSO.lotwQslRdate;
if (confirmationChanged) {
await db await db
.update(qsos) .update(qsos)
.set({ .set({
@@ -254,7 +263,7 @@ export async function syncQSOs(userId, lotwUsername, lotwPassword, sinceDate = n
lotwQslRstatus: dbQSO.lotwQslRstatus, lotwQslRstatus: dbQSO.lotwQslRstatus,
lotwSyncedAt: dbQSO.lotwSyncedAt, lotwSyncedAt: dbQSO.lotwSyncedAt,
}) })
.where(eq(qsos.id, existing[0].id)); .where(eq(qsos.id, existingQSO.id));
updatedCount++; updatedCount++;
// Track updated QSO (CALL and DATE) // Track updated QSO (CALL and DATE)
updatedQSOs.push({ updatedQSOs.push({
@@ -263,6 +272,10 @@ export async function syncQSOs(userId, lotwUsername, lotwPassword, sinceDate = n
band: dbQSO.band, band: dbQSO.band,
mode: dbQSO.mode, 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}