From f09eaaad7cb70f60c1430db07935f5d568c6b7b2 Mon Sep 17 00:00:00 2001 From: AI Assistant Date: Sun, 8 Mar 2026 00:46:02 +0200 Subject: [PATCH] feat: enrichment fallback via direct parcel details endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PROBLEM: For no-geometry parcels (and many geometry parcels without application IDs), CATEGORIE_FOLOSINTA was always '-' because: 1. fetchImmAppsByImmovable returned no apps (no applicationId) 2. Without appId, fetchParcelFolosinte was skipped entirely 3. No fallback existed DISCOVERY (from eTerra UI investigation): The endpoint /api/immovable/details/parcels/list/{wp}/{pk}/{page}/{size} returns parcel use categories DIRECTLY — no applicationId needed. Example: [{useCategory:'arabil', intravilan:'Necunoscut', parcelPk:17753903}] FIX: - After the app-based CATEGORIE_FOLOSINTA attempt, if result is still '-', fall back to fetchImmovableParcelDetails (the direct endpoint) - formatCategories now handles both API formats: - App-based: categorieFolosinta + suprafata fields - Direct: useCategory field (no area — shows category name only) - When direct endpoint provides area=0, format shows just the category name without ':0' (e.g. 'arabil; faneata' instead of 'arabil:0; faneata:0') - Also picks up intravilan from direct endpoint if app-based was empty - Fixed fetchImmovableParcelDetails default size: 1 → 20 (one immovable can have multiple parcels, e.g. IE 25332 has 2: arabil + faneata) - Results are cached in folCache to avoid duplicate requests --- .../parcel-sync/services/enrich-service.ts | 43 ++++++++++++++++++- .../parcel-sync/services/eterra-client.ts | 6 +-- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/modules/parcel-sync/services/enrich-service.ts b/src/modules/parcel-sync/services/enrich-service.ts index 5601811..f46dee8 100644 --- a/src/modules/parcel-sync/services/enrich-service.ts +++ b/src/modules/parcel-sync/services/enrich-service.ts @@ -80,13 +80,18 @@ const normalizeIntravilan = (values: string[]) => { const formatCategories = (entries: any[]) => { const map = new Map(); for (const entry of entries) { - const key = String(entry?.categorieFolosinta ?? "").trim(); + // Support both API formats: + // fetchParcelFolosinte (via app): categorieFolosinta + suprafata + // fetchImmovableParcelDetails (direct): useCategory (no area) + const key = String( + entry?.categorieFolosinta ?? entry?.useCategory ?? "", + ).trim(); if (!key) continue; const area = Number(entry?.suprafata ?? 0); map.set(key, (map.get(key) ?? 0) + (Number.isFinite(area) ? area : 0)); } return Array.from(map.entries()) - .map(([k, a]) => `${k}:${formatNumber(a)}`) + .map(([k, a]) => (a > 0 ? `${k}:${formatNumber(a)}` : k)) .join("; "); }; @@ -482,6 +487,40 @@ export async function enrichFeatures( ); 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 = + 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, + immPkForDetails as string | number, + ), + ); + } catch { + details = []; + } + 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; + } + } } const cadRefRaw = (attrs.NATIONAL_CADASTRAL_REFERENCE ?? "") as string; diff --git a/src/modules/parcel-sync/services/eterra-client.ts b/src/modules/parcel-sync/services/eterra-client.ts index 2e63682..abd4076 100644 --- a/src/modules/parcel-sync/services/eterra-client.ts +++ b/src/modules/parcel-sync/services/eterra-client.ts @@ -362,9 +362,7 @@ export class EterraClient { await sleep(500); // small delay before retry with smaller page continue; } - throw new Error( - `Failed to fetch layer ${layer.name}: ${cause}`, - ); + throw new Error(`Failed to fetch layer ${layer.name}: ${cause}`); } const features = data.features ?? []; @@ -504,7 +502,7 @@ export class EterraClient { workspaceId: string | number, immovableId: string | number, page = 1, - size = 1, + size = 20, ): Promise { const url = `${BASE_URL}/api/immovable/details/parcels/list/${workspaceId}/${immovableId}/${page}/${size}`; return this.getRawJson(url);