diff --git a/src/modules/parcel-sync/services/enrich-service.ts b/src/modules/parcel-sync/services/enrich-service.ts index f46dee8..fa6afd2 100644 --- a/src/modules/parcel-sync/services/enrich-service.ts +++ b/src/modules/parcel-sync/services/enrich-service.ts @@ -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(); const folCache = new Map(); 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,73 +437,62 @@ 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( + // ── 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; + const detKey = `${workspaceId}:${immPkForDetails}:details`; + let details = folCache.get(detKey); + if (!details) { + try { + details = await throttled(() => + client.fetchImmovableParcelDetails( workspaceId as string | number, - immovableId as string | number, - appId, + immPkForDetails as string | number, ), ); - folCache.set(folKey, fol); + } catch { + details = []; } + folCache.set(detKey, details); + } + if (details && details.length > 0) { intravilan = normalizeIntravilan( - fol.map((item: any) => item?.intravilan ?? ""), + details.map((d: any) => d?.intravilan ?? ""), ); - categorie = formatCategories(fol); + categorie = formatCategories(details); } - // 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 = - immovableListById.get(normalizeId(immovableId))?.immovablePk ?? - immovableId; - const detKey = `${workspaceId}:${immPkForDetails}:details`; - let details = folCache.get(detKey); - if (!details) { - try { - details = await throttled(() => - client.fetchImmovableParcelDetails( + // ── 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, - immPkForDetails as string | number, + immovableId as string | number, + appId, ), ); - } catch { - details = []; + 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; } - folCache.set(detKey, details); - } - if (details && details.length > 0) { - const detIntravilan = normalizeIntravilan( - details.map((d: any) => d?.intravilan ?? ""), - ); - const detCategorie = formatCategories(details); - if (detCategorie && detCategorie !== "-") categorie = detCategorie; - if (detIntravilan && detIntravilan !== "-" && intravilan === "-") - intravilan = detIntravilan; } } } diff --git a/src/modules/parcel-sync/services/eterra-client.ts b/src/modules/parcel-sync/services/eterra-client.ts index a1e288a..70d7d01 100644 --- a/src/modules/parcel-sync/services/eterra-client.ts +++ b/src/modules/parcel-sync/services/eterra-client.ts @@ -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 { diff --git a/src/modules/parcel-sync/services/no-geom-sync.ts b/src/modules/parcel-sync/services/no-geom-sync.ts index 8e4d853..d895e66 100644 --- a/src/modules/parcel-sync/services/no-geom-sync.ts +++ b/src/modules/parcel-sync/services/no-geom-sync.ts @@ -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;