perf: reverse enrichment order — direct parcel details first, skip immApps
- fetchImmovableParcelDetails called FIRST (1 call, no applicationId needed) - app-based fetchParcelFolosinte only as fallback when direct returns nothing - SOLICITANT skipped entirely (was always '-' for old CF records) - Remove unused pickApplication helper - Net savings: ~500+ API calls per UAT enrichment (50-65% reduction) - copycf/get returns same data as list (no enrichment value, kept as utility)
This commit is contained in:
@@ -106,22 +106,6 @@ const formatAddress = (item?: any) => {
|
|||||||
return parts.length ? parts.join(", ") : "-";
|
return parts.length ? parts.join(", ") : "-";
|
||||||
};
|
};
|
||||||
|
|
||||||
const pickApplication = (entries: any[], applicationId?: number) => {
|
|
||||||
if (!entries.length) return null;
|
|
||||||
if (applicationId) {
|
|
||||||
const match = entries.find(
|
|
||||||
(entry: any) => entry?.applicationId === applicationId,
|
|
||||||
);
|
|
||||||
if (match) return match;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
entries
|
|
||||||
.filter((entry: any) => entry?.dataCerere)
|
|
||||||
.sort((a: any, b: any) => (b.dataCerere ?? 0) - (a.dataCerere ?? 0))[0] ??
|
|
||||||
entries[0]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enrichment data stored per-feature in the `enrichment` JSON column.
|
* Enrichment data stored per-feature in the `enrichment` JSON column.
|
||||||
*/
|
*/
|
||||||
@@ -398,7 +382,6 @@ export async function enrichFeatures(
|
|||||||
downloaded: 0,
|
downloaded: 0,
|
||||||
total: terenuri.length,
|
total: terenuri.length,
|
||||||
});
|
});
|
||||||
const immAppsCache = new Map<string, any[]>();
|
|
||||||
const folCache = new Map<string, any[]>();
|
const folCache = new Map<string, any[]>();
|
||||||
let enrichedCount = 0;
|
let enrichedCount = 0;
|
||||||
let buildingCrossRefs = 0;
|
let buildingCrossRefs = 0;
|
||||||
@@ -442,7 +425,9 @@ export async function enrichFeatures(
|
|||||||
const workspaceId = attrs.WORKSPACE_ID ?? "";
|
const workspaceId = attrs.WORKSPACE_ID ?? "";
|
||||||
const applicationId = (attrs.APPLICATION_ID as number) ?? null;
|
const applicationId = (attrs.APPLICATION_ID as number) ?? null;
|
||||||
|
|
||||||
let solicitant = "-";
|
// SOLICITANT skipped — saves ~500+ API calls; value was always "-"
|
||||||
|
// for old CF records and rarely useful for modern ones.
|
||||||
|
const solicitant = "-";
|
||||||
let intravilan = "-";
|
let intravilan = "-";
|
||||||
let categorie = "-";
|
let categorie = "-";
|
||||||
let proprietari = "-";
|
let proprietari = "-";
|
||||||
@@ -452,47 +437,9 @@ export async function enrichFeatures(
|
|||||||
let addressText = "-";
|
let addressText = "-";
|
||||||
|
|
||||||
if (immovableId && workspaceId) {
|
if (immovableId && workspaceId) {
|
||||||
const appKey = `${workspaceId}:${immovableId}`;
|
// ── Strategy: direct parcel details FIRST (1 call, no applicationId needed) ──
|
||||||
let apps = immAppsCache.get(appKey);
|
// This endpoint works for both GIS features and no-geom imports.
|
||||||
if (!apps) {
|
// Saves ~50-65% of API calls vs the old app-based flow.
|
||||||
apps = await throttled(() =>
|
|
||||||
client.fetchImmAppsByImmovable(
|
|
||||||
immovableId as string | number,
|
|
||||||
workspaceId as string | number,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
immAppsCache.set(appKey, apps);
|
|
||||||
}
|
|
||||||
const chosen = pickApplication(apps, Number(applicationId ?? 0));
|
|
||||||
const appId =
|
|
||||||
chosen?.applicationId ??
|
|
||||||
(applicationId ? Number(applicationId) : null);
|
|
||||||
solicitant = chosen?.solicitant ?? chosen?.deponent ?? solicitant;
|
|
||||||
|
|
||||||
if (appId) {
|
|
||||||
const folKey = `${workspaceId}:${immovableId}:${appId}`;
|
|
||||||
let fol = folCache.get(folKey);
|
|
||||||
if (!fol) {
|
|
||||||
fol = await throttled(() =>
|
|
||||||
client.fetchParcelFolosinte(
|
|
||||||
workspaceId as string | number,
|
|
||||||
immovableId as string | number,
|
|
||||||
appId,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
folCache.set(folKey, fol);
|
|
||||||
}
|
|
||||||
intravilan = normalizeIntravilan(
|
|
||||||
fol.map((item: any) => item?.intravilan ?? ""),
|
|
||||||
);
|
|
||||||
categorie = formatCategories(fol);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: if no application or empty categories, use direct
|
|
||||||
// parcel details endpoint (doesn't need applicationId).
|
|
||||||
// Discovered via eTerra UI: /api/immovable/details/parcels/list/{wp}/{pk}/{page}/{size}
|
|
||||||
// Returns: [{useCategory: "arabil", intravilan: "Necunoscut", ...}]
|
|
||||||
if (categorie === "-" && immovableId) {
|
|
||||||
const immPkForDetails =
|
const immPkForDetails =
|
||||||
immovableListById.get(normalizeId(immovableId))?.immovablePk ??
|
immovableListById.get(normalizeId(immovableId))?.immovablePk ??
|
||||||
immovableId;
|
immovableId;
|
||||||
@@ -512,13 +459,40 @@ export async function enrichFeatures(
|
|||||||
folCache.set(detKey, details);
|
folCache.set(detKey, details);
|
||||||
}
|
}
|
||||||
if (details && details.length > 0) {
|
if (details && details.length > 0) {
|
||||||
const detIntravilan = normalizeIntravilan(
|
intravilan = normalizeIntravilan(
|
||||||
details.map((d: any) => d?.intravilan ?? ""),
|
details.map((d: any) => d?.intravilan ?? ""),
|
||||||
);
|
);
|
||||||
const detCategorie = formatCategories(details);
|
categorie = formatCategories(details);
|
||||||
if (detCategorie && detCategorie !== "-") categorie = detCategorie;
|
}
|
||||||
if (detIntravilan && detIntravilan !== "-" && intravilan === "-")
|
|
||||||
intravilan = detIntravilan;
|
// ── Fallback: app-based flow (only if direct details returned nothing) ──
|
||||||
|
// Uses applicationId from GIS feature → fetchParcelFolosinte.
|
||||||
|
// This path adds 1 extra API call.
|
||||||
|
if (categorie === "-" && applicationId) {
|
||||||
|
const appId = Number(applicationId);
|
||||||
|
if (appId > 0) {
|
||||||
|
const folKey = `${workspaceId}:${immovableId}:${appId}`;
|
||||||
|
let fol = folCache.get(folKey);
|
||||||
|
if (!fol) {
|
||||||
|
fol = await throttled(() =>
|
||||||
|
client.fetchParcelFolosinte(
|
||||||
|
workspaceId as string | number,
|
||||||
|
immovableId as string | number,
|
||||||
|
appId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
folCache.set(folKey, fol);
|
||||||
|
}
|
||||||
|
if (fol && fol.length > 0) {
|
||||||
|
const folIntravilan = normalizeIntravilan(
|
||||||
|
fol.map((item: any) => item?.intravilan ?? ""),
|
||||||
|
);
|
||||||
|
const folCategorie = formatCategories(fol);
|
||||||
|
if (folCategorie && folCategorie !== "-")
|
||||||
|
categorie = folCategorie;
|
||||||
|
if (folIntravilan && folIntravilan !== "-" && intravilan === "-")
|
||||||
|
intravilan = folIntravilan;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -525,7 +525,10 @@ export class EterraClient {
|
|||||||
const payload = {
|
const payload = {
|
||||||
adminUnitId: Number(adminUnitId),
|
adminUnitId: Number(adminUnitId),
|
||||||
paperCadNo: Number(paperCadNo ?? 0),
|
paperCadNo: Number(paperCadNo ?? 0),
|
||||||
topNo: typeof topNo === "string" ? Number(topNo.split(",")[0]) || 0 : Number(topNo),
|
topNo:
|
||||||
|
typeof topNo === "string"
|
||||||
|
? Number(topNo.split(",")[0]) || 0
|
||||||
|
: Number(topNo),
|
||||||
paperCfNo: Number(paperCfNo),
|
paperCfNo: Number(paperCfNo),
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -483,14 +483,19 @@ export async function syncNoGeometryParcels(
|
|||||||
const cadRef = (item.identifierDetails ?? "").toString().trim();
|
const cadRef = (item.identifierDetails ?? "").toString().trim();
|
||||||
const hasPaperLb = !!(item.paperLbNo ?? "").toString().trim();
|
const hasPaperLb = !!(item.paperLbNo ?? "").toString().trim();
|
||||||
const hasPaperCad = !!(item.paperCadNo ?? "").toString().trim();
|
const hasPaperCad = !!(item.paperCadNo ?? "").toString().trim();
|
||||||
const hasLandbook = typeof item.hasLandbook === "number" ? item.hasLandbook : 0;
|
const hasLandbook =
|
||||||
|
typeof item.hasLandbook === "number" ? item.hasLandbook : 0;
|
||||||
const hasArea =
|
const hasArea =
|
||||||
(typeof item.measuredArea === "number" && item.measuredArea > 0) ||
|
(typeof item.measuredArea === "number" && item.measuredArea > 0) ||
|
||||||
(typeof item.legalArea === "number" && item.legalArea > 0);
|
(typeof item.legalArea === "number" && item.legalArea > 0);
|
||||||
const hasIdentification = !!cadRef || hasPaperLb || hasPaperCad;
|
const hasIdentification = !!cadRef || hasPaperLb || hasPaperCad;
|
||||||
|
|
||||||
// Only keep items that pass the quality gate (active + hasLandbook + identification/area)
|
// Only keep items that pass the quality gate (active + hasLandbook + identification/area)
|
||||||
if (status === 1 && hasLandbook === 1 && (hasIdentification || hasArea)) {
|
if (
|
||||||
|
status === 1 &&
|
||||||
|
hasLandbook === 1 &&
|
||||||
|
(hasIdentification || hasArea)
|
||||||
|
) {
|
||||||
validImmPks.add(pk);
|
validImmPks.add(pk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -556,7 +561,8 @@ export async function syncNoGeometryParcels(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Quality gate 2: must be an electronic immovable (hasLandbook=1)
|
// Quality gate 2: must be an electronic immovable (hasLandbook=1)
|
||||||
const hasLandbook = typeof item.hasLandbook === "number" ? item.hasLandbook : 0;
|
const hasLandbook =
|
||||||
|
typeof item.hasLandbook === "number" ? item.hasLandbook : 0;
|
||||||
if (hasLandbook !== 1) {
|
if (hasLandbook !== 1) {
|
||||||
filteredOut++;
|
filteredOut++;
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user