fix: remove all hardcoded workspaceId=65 + add robustness for large UATs

- enrich-service: resolve workspacePk from feature attrs / GisUat DB / ArcGIS
  (was hardcoded 65, broke enrichment for non-BN counties)
- enrich-service: skip already-enriched features (resume after crash)
- no-geom-sync: use resolved wsPk in synthetic attributes
- no-geom-sync: batched DB inserts (50/batch) with retry + exponential backoff
- Fixes: Magic export for Cluj/other counties getting empty enrichment
This commit is contained in:
AI Assistant
2026-03-07 17:17:55 +02:00
parent db6ac5d3a3
commit 40b9522e12
2 changed files with 158 additions and 61 deletions
@@ -252,77 +252,109 @@ export async function syncNoGeometryParcels(
return { imported: 0, skipped: 0, errors: 0, status: "done" };
}
// 4. Import candidates
// 4. Import candidates in batches with retry
let imported = 0;
let skipped = 0;
let errors = 0;
const total = candidates.length;
const BATCH_SIZE = 50;
const MAX_RETRIES = 3;
for (let i = 0; i < candidates.length; i++) {
const item = candidates[i]!;
const immPk = Number(item.immovablePk ?? 0);
if (immPk <= 0) {
skipped++;
continue;
}
for (
let batchStart = 0;
batchStart < candidates.length;
batchStart += BATCH_SIZE
) {
const batch = candidates.slice(batchStart, batchStart + BATCH_SIZE);
const ops: Array<ReturnType<typeof prisma.gisFeature.upsert>> = [];
const cadRef = String(item.identifierDetails ?? "").trim();
const areaValue = typeof item.area === "number" ? item.area : null;
for (const item of batch) {
const immPk = Number(item.immovablePk ?? 0);
if (immPk <= 0) {
skipped++;
continue;
}
// Build synthetic attributes to match the eTerra GIS layer format
const attributes: Record<string, unknown> = {
OBJECTID: -immPk, // synthetic negative
IMMOVABLE_ID: immPk,
WORKSPACE_ID: item.workspacePk ?? 65,
APPLICATION_ID: item.applicationId ?? null,
NATIONAL_CADASTRAL_REFERENCE: cadRef,
AREA_VALUE: areaValue,
IS_ACTIVE: 1,
ADMIN_UNIT_ID: Number(siruta),
// Metadata from immovable list
PAPER_CAD_NO: item.paperCadNo ?? null,
PAPER_CF_NO: item.paperCfNo ?? null,
PAPER_LB_NO: item.paperLbNo ?? null,
TOP_NO: item.topNo ?? null,
IMMOVABLE_TYPE: item.immovableType ?? "P",
NO_GEOMETRY_SOURCE: "ETERRA_IMMOVABLE_LIST",
};
const cadRef = String(item.identifierDetails ?? "").trim();
const areaValue = typeof item.area === "number" ? item.area : null;
try {
await prisma.gisFeature.upsert({
where: {
layerId_objectId: {
layerId: "TERENURI_ACTIVE",
objectId: -immPk,
const attributes: Record<string, unknown> = {
OBJECTID: -immPk,
IMMOVABLE_ID: immPk,
WORKSPACE_ID: item.workspacePk ?? wsPk,
APPLICATION_ID: item.applicationId ?? null,
NATIONAL_CADASTRAL_REFERENCE: cadRef,
AREA_VALUE: areaValue,
IS_ACTIVE: 1,
ADMIN_UNIT_ID: Number(siruta),
PAPER_CAD_NO: item.paperCadNo ?? null,
PAPER_CF_NO: item.paperCfNo ?? null,
PAPER_LB_NO: item.paperLbNo ?? null,
TOP_NO: item.topNo ?? null,
IMMOVABLE_TYPE: item.immovableType ?? "P",
NO_GEOMETRY_SOURCE: "ETERRA_IMMOVABLE_LIST",
};
ops.push(
prisma.gisFeature.upsert({
where: {
layerId_objectId: {
layerId: "TERENURI_ACTIVE",
objectId: -immPk,
},
},
},
create: {
layerId: "TERENURI_ACTIVE",
siruta,
objectId: -immPk,
cadastralRef: cadRef || null,
areaValue,
isActive: true,
attributes: attributes as Prisma.InputJsonValue,
geometry: Prisma.JsonNull,
geometrySource: "NO_GEOMETRY",
},
update: {
cadastralRef: cadRef || null,
areaValue,
attributes: attributes as Prisma.InputJsonValue,
geometrySource: "NO_GEOMETRY",
updatedAt: new Date(),
},
});
imported++;
} catch {
errors++;
create: {
layerId: "TERENURI_ACTIVE",
siruta,
objectId: -immPk,
cadastralRef: cadRef || null,
areaValue,
isActive: true,
attributes: attributes as Prisma.InputJsonValue,
geometry: Prisma.JsonNull,
geometrySource: "NO_GEOMETRY",
},
update: {
cadastralRef: cadRef || null,
areaValue,
attributes: attributes as Prisma.InputJsonValue,
geometrySource: "NO_GEOMETRY",
updatedAt: new Date(),
},
}),
);
}
if (i % 20 === 0 || i === total - 1) {
options?.onProgress?.(i + 1, total, "Import parcele fără geometrie");
// Execute batch with retry
if (ops.length > 0) {
let attempt = 0;
while (attempt < MAX_RETRIES) {
try {
await prisma.$transaction(ops);
imported += ops.length;
break;
} catch (err) {
attempt++;
if (attempt >= MAX_RETRIES) {
// Fall back to individual upserts for this batch
for (const op of ops) {
try {
await op;
imported++;
} catch {
errors++;
}
}
} else {
// Wait before retry (exponential backoff)
await new Promise((r) => setTimeout(r, 500 * attempt));
}
}
}
}
const done = Math.min(batchStart + BATCH_SIZE, total);
options?.onProgress?.(done, total, "Import parcele fără geometrie");
}
return { imported, skipped, errors, status: "done" };