fix(parcel-sync): always run syncLayer for delta detection + no-geom freshness

- Always call syncLayer for TERENURI/CLADIRI (not gated by isFresh)
  so that quick-count + VALID_FROM delta actually run on daily syncs
- syncLayer handles efficiency internally via quick-count match
- Add 48h freshness check for no-geom import (skip if recent)
- Admin layers: skip if synced within 24h
- Log sync summary (new features, updated features)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-30 23:25:23 +03:00
parent f822509169
commit 7a93a28055
+50 -26
View File
@@ -187,42 +187,38 @@ async function runBackground(params: {
getLayerFreshness(siruta, "CLADIRI_ACTIVE"), getLayerFreshness(siruta, "CLADIRI_ACTIVE"),
]); ]);
const terenuriNeedsSync = const terenuriNeedsFullSync =
forceSync || forceSync || terenuriStatus.featureCount === 0;
!isFresh(terenuriStatus.lastSynced) || const cladiriNeedsFullSync =
terenuriStatus.featureCount === 0; forceSync || cladiriStatus.featureCount === 0;
const cladiriNeedsSync =
forceSync ||
!isFresh(cladiriStatus.lastSynced) ||
cladiriStatus.featureCount === 0;
if (terenuriNeedsSync) { // Always call syncLayer — it handles quick-count + VALID_FROM delta internally.
// Only force full download when no local data or explicit forceSync.
phase = "Sincronizare terenuri"; phase = "Sincronizare terenuri";
push({}); push({});
const r = await syncLayer(username, password, siruta, "TERENURI_ACTIVE", { const terenuriResult = await syncLayer(username, password, siruta, "TERENURI_ACTIVE", {
forceFullSync: forceSync, forceFullSync: terenuriNeedsFullSync,
jobId, jobId,
isSubStep: true, isSubStep: true,
}); });
if (r.status === "error") if (terenuriResult.status === "error")
throw new Error(r.error ?? "Sync terenuri failed"); throw new Error(terenuriResult.error ?? "Sync terenuri failed");
}
updateOverall(0.5); updateOverall(0.5);
if (cladiriNeedsSync) {
phase = "Sincronizare clădiri"; phase = "Sincronizare clădiri";
push({}); push({});
const r = await syncLayer(username, password, siruta, "CLADIRI_ACTIVE", { const cladiriResult = await syncLayer(username, password, siruta, "CLADIRI_ACTIVE", {
forceFullSync: forceSync, forceFullSync: cladiriNeedsFullSync,
jobId, jobId,
isSubStep: true, isSubStep: true,
}); });
if (r.status === "error") if (cladiriResult.status === "error")
throw new Error(r.error ?? "Sync clădiri failed"); throw new Error(cladiriResult.error ?? "Sync clădiri failed");
}
// Sync admin layers (always, lightweight) // Sync admin layers — skip if synced within 24h
for (const adminLayer of ["LIMITE_INTRAV_DYNAMIC", "LIMITE_UAT"]) { for (const adminLayer of ["LIMITE_INTRAV_DYNAMIC", "LIMITE_UAT"]) {
const adminStatus = await getLayerFreshness(siruta, adminLayer);
if (!forceSync && isFresh(adminStatus.lastSynced, 24)) continue;
phase = `Sincronizare ${adminLayer === "LIMITE_UAT" ? "limite UAT" : "limite intravilan"}`; phase = `Sincronizare ${adminLayer === "LIMITE_UAT" ? "limite UAT" : "limite intravilan"}`;
push({}); push({});
try { try {
@@ -232,20 +228,47 @@ async function runBackground(params: {
isSubStep: true, isSubStep: true,
}); });
} catch { } catch {
// Non-critical — don't fail the whole job
note = `Avertisment: ${adminLayer} nu s-a sincronizat`; note = `Avertisment: ${adminLayer} nu s-a sincronizat`;
push({}); push({});
} }
} }
if (!terenuriNeedsSync && !cladiriNeedsSync) { const syncSummary = [
note = "Date proaspete — sync skip"; terenuriResult.newFeatures > 0 ? `${terenuriResult.newFeatures} terenuri noi` : null,
} terenuriResult.validFromUpdated ? `${terenuriResult.validFromUpdated} terenuri actualizate` : null,
cladiriResult.newFeatures > 0 ? `${cladiriResult.newFeatures} cladiri noi` : null,
cladiriResult.validFromUpdated ? `${cladiriResult.validFromUpdated} cladiri actualizate` : null,
].filter(Boolean);
note = syncSummary.length > 0 ? syncSummary.join(", ") : "Fără schimbări";
finishPhase(); finishPhase();
/* ── Phase 2: No-geometry import (optional) ──────── */ /* ── Phase 2: No-geometry import (optional) ──────── */
if (hasNoGeom && weights.noGeom > 0) { if (hasNoGeom && weights.noGeom > 0) {
setPhase("Import parcele fără geometrie", weights.noGeom); setPhase("Verificare parcele fără geometrie", weights.noGeom);
// Skip no-geom import if recently done (within 48h) and not forced
const { PrismaClient } = await import("@prisma/client");
const _prisma = new PrismaClient();
let skipNoGeom = false;
try {
const recentNoGeom = await _prisma.gisFeature.findFirst({
where: {
layerId: "TERENURI_ACTIVE",
siruta,
geometrySource: "NO_GEOMETRY",
updatedAt: { gte: new Date(Date.now() - 48 * 60 * 60 * 1000) },
},
select: { id: true },
});
skipNoGeom = !forceSync && recentNoGeom != null;
} catch { /* proceed with import */ }
await _prisma.$disconnect();
if (skipNoGeom) {
note = "Parcele fără geometrie — actualizate recent, skip";
push({});
} else {
phase = "Import parcele fără geometrie";
push({});
const noGeomClient = await EterraClient.create(username, password, { const noGeomClient = await EterraClient.create(username, password, {
timeoutMs: 120_000, timeoutMs: 120_000,
}); });
@@ -264,6 +287,7 @@ async function runBackground(params: {
note = `${res.imported} parcele noi importate${cleanNote}`; note = `${res.imported} parcele noi importate${cleanNote}`;
push({}); push({});
} }
}
finishPhase(); finishPhase();
} }