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(", ") : "-";
|
||||
};
|
||||
|
||||
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.
|
||||
*/
|
||||
@@ -398,7 +382,6 @@ export async function enrichFeatures(
|
||||
downloaded: 0,
|
||||
total: terenuri.length,
|
||||
});
|
||||
const immAppsCache = new Map<string, any[]>();
|
||||
const folCache = new Map<string, any[]>();
|
||||
let enrichedCount = 0;
|
||||
let buildingCrossRefs = 0;
|
||||
@@ -442,7 +425,9 @@ export async function enrichFeatures(
|
||||
const workspaceId = attrs.WORKSPACE_ID ?? "";
|
||||
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 categorie = "-";
|
||||
let proprietari = "-";
|
||||
@@ -452,47 +437,9 @@ export async function enrichFeatures(
|
||||
let addressText = "-";
|
||||
|
||||
if (immovableId && workspaceId) {
|
||||
const appKey = `${workspaceId}:${immovableId}`;
|
||||
let apps = immAppsCache.get(appKey);
|
||||
if (!apps) {
|
||||
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) {
|
||||
// ── Strategy: direct parcel details FIRST (1 call, no applicationId needed) ──
|
||||
// This endpoint works for both GIS features and no-geom imports.
|
||||
// Saves ~50-65% of API calls vs the old app-based flow.
|
||||
const immPkForDetails =
|
||||
immovableListById.get(normalizeId(immovableId))?.immovablePk ??
|
||||
immovableId;
|
||||
@@ -512,13 +459,40 @@ export async function enrichFeatures(
|
||||
folCache.set(detKey, details);
|
||||
}
|
||||
if (details && details.length > 0) {
|
||||
const detIntravilan = normalizeIntravilan(
|
||||
intravilan = normalizeIntravilan(
|
||||
details.map((d: any) => d?.intravilan ?? ""),
|
||||
);
|
||||
const detCategorie = formatCategories(details);
|
||||
if (detCategorie && detCategorie !== "-") categorie = detCategorie;
|
||||
if (detIntravilan && detIntravilan !== "-" && intravilan === "-")
|
||||
intravilan = detIntravilan;
|
||||
categorie = formatCategories(details);
|
||||
}
|
||||
|
||||
// ── 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 = {
|
||||
adminUnitId: Number(adminUnitId),
|
||||
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),
|
||||
};
|
||||
try {
|
||||
|
||||
@@ -483,14 +483,19 @@ export async function syncNoGeometryParcels(
|
||||
const cadRef = (item.identifierDetails ?? "").toString().trim();
|
||||
const hasPaperLb = !!(item.paperLbNo ?? "").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 =
|
||||
(typeof item.measuredArea === "number" && item.measuredArea > 0) ||
|
||||
(typeof item.legalArea === "number" && item.legalArea > 0);
|
||||
const hasIdentification = !!cadRef || hasPaperLb || hasPaperCad;
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -556,7 +561,8 @@ export async function syncNoGeometryParcels(
|
||||
}
|
||||
|
||||
// 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) {
|
||||
filteredOut++;
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user