diff --git a/src/app/api/eterra/session/county-refresh.ts b/src/app/api/eterra/session/county-refresh.ts index d57cb07..e3acfcd 100644 --- a/src/app/api/eterra/session/county-refresh.ts +++ b/src/app/api/eterra/session/county-refresh.ts @@ -1,89 +1,23 @@ /** - * County refresh — populates GisUat.county from eTerra LIMITE_UAT layer. + * County refresh — populates GisUat.county from eTerra. * * Called with an already-authenticated EterraClient (fire-and-forget * after login), so there's no session expiry risk. * - * Strategy: - * 1. Query LIMITE_UAT for all features (no geometry) → get ADMIN_UNIT_ID + WORKSPACE_ID per UAT - * 2. We have 42 unique WORKSPACE_IDs = 42 counties. Resolve names by - * looking up known UATs per workspace (e.g. WORKSPACE_ID 127 contains - * CLUJ-NAPOCA → county is "Cluj"). - * 3. For initial bootstrap: use a well-known UAT→county mapping to seed - * the workspace→county lookup, then verify/extend from LIMITE_UAT data. + * Strategy (100% from eTerra, zero hardcoding): + * 1. Query LIMITE_UAT for all features (no geometry) → + * get ADMIN_UNIT_ID (SIRUTA) + WORKSPACE_ID per UAT + * 2. Group by WORKSPACE_ID → 42 unique workspaces (= 42 counties) + * 3. For each workspace, pick one SIRUTA and call + * fetchImmovableListByAdminUnit(workspaceId, siruta, page=0, size=1) + * → response includes workspace.name = county name + * 4. Batch-update GisUat.county + workspacePk */ import { prisma } from "@/core/storage/prisma"; import type { EterraClient } from "@/modules/parcel-sync/services/eterra-client"; import { findLayerById } from "@/modules/parcel-sync/services/eterra-layers"; -/** - * Well-known UAT SIRUTAs that uniquely identify each county. - * One county seat (reședință de județ) per county — these names are unambiguous. - * Used to bootstrap the WORKSPACE_ID → county name mapping. - */ -const COUNTY_SEATS: Record = { - // SIRUTA → County name - "1015": "Alba", // ALBA IULIA - "10405": "Arad", // ARAD - "21082": "Argeș", // PITEȘTI - "30732": "Bacău", // BACĂU - "27006": "Bihor", // ORADEA - "32394": "Bistrița-Năsăud", // BISTRIȚA - "40052": "Botoșani", // BOTOȘANI - "46085": "Brașov", // BRAȘOV - "50714": "Brăila", // BRĂILA - "52451": "Buzău", // BUZĂU - "63429": "Caraș-Severin", // REȘIȚA - "54975": "Cluj", // CLUJ-NAPOCA - "60724": "Constanța", // CONSTANȚA - "66378": "Covasna", // SFÂNTU GHEORGHE - "71102": "Dâmbovița", // TÂRGOVIȘTE - "75091": "Dolj", // CRAIOVA - "80200": "Galați", // GALAȚI - "82747": "Gorj", // TÂRGU JIU - "84284": "Harghita", // MIERCUREA CIUC - "87233": "Hunedoara", // DEVA - "91589": "Ialomița", // SLOBOZIA - "93920": "Iași", // IAȘI - "100091": "Ilfov", // BUFTEA — reședință Ilfov (nu București) - "108013": "Maramureș", // BAIA MARE - "110613": "Mehedinți", // DROBETA-TURNU SEVERIN - "114970": "Mureș", // ACĂȚARI — fallback if TG MUREȘ not matched - "119030": "Neamț", // PIATRA NEAMȚ - "121820": "Olt", // SLATINA - "125270": "Prahova", // PLOIEȘTI - "136458": "Satu Mare", // SATU MARE - "134243": "Sălaj", // ZALĂU - "131740": "Sibiu", // SIBIU - "100754": "Suceava", // ADÂNCATA — fallback - "141839": "Teleorman", // ALEXANDRIA - "144490": "Timiș", // TIMIȘOARA - "148459": "Tulcea", // TULCEA - "161829": "Vaslui", // HUȘI — confirmed in DB - "152886": "Vâlcea", // RÂMNICU VÂLCEA - "155113": "Vrancea", // FOCȘANI - "179141": "București", // BUCUREȘTI SECTORUL 1 - "51461": "Călărași", // CĂLĂRAȘI - "82278": "Giurgiu", // GIURGIU — corectat SIRUTA -}; - -/** Alternate well-known UATs for counties hard to match by seats */ -const COUNTY_ALTS: Record = { - "115870": "Mureș", // TÂRGU MUREȘ - "145721": "Timiș", // TIMIȘOARA - "146799": "Vrancea", // ADÂNCATA (Vrancea) - "147358": "Mehedinți", // BROȘTENI (Mehedinți) — confirmed - "175466": "Vrancea", // BROȘTENI (Vrancea) — confirmed - "87246": "Hunedoara", // BĂNIȚA — confirmed - "57582": "Cluj", // FELEACU — confirmed - "55598": "Cluj", // AITON — confirmed - "33248": "Bistrița-Năsăud", // FELDRU — confirmed - "34903": "Bistrița-Năsăud", // ȘINTEREAG — confirmed - "33177": "Bistrița-Năsăud", // COȘBUC — confirmed - "100754": "Suceava", // ADÂNCATA (Suceava) — confirmed -}; - function titleCase(s: string): string { return s .toLowerCase() @@ -105,14 +39,12 @@ export async function refreshCountyData(client: EterraClient): Promise { return; } - console.log( - `[county-refresh] Starting: ${withCounty}/${total} have county.`, - ); + console.log(`[county-refresh] Starting: ${withCounty}/${total} have county.`); // 1. Query LIMITE_UAT for ADMIN_UNIT_ID + WORKSPACE_ID const limiteUat = findLayerById("LIMITE_UAT"); if (!limiteUat) { - console.error("[county-refresh] LIMITE_UAT layer not configured"); + console.error("[county-refresh] LIMITE_UAT layer not configured."); return; } @@ -123,9 +55,9 @@ export async function refreshCountyData(client: EterraClient): Promise { }); console.log(`[county-refresh] LIMITE_UAT: ${features.length} features.`); + if (features.length === 0) return; - // 2. Build SIRUTA → WORKSPACE_ID map, and collect unique workspaces - const sirutaToWs = new Map(); + // 2. Group SIRUTAs by WORKSPACE_ID const wsToSirutas = new Map(); for (const f of features) { @@ -135,48 +67,61 @@ export async function refreshCountyData(client: EterraClient): Promise { const ws = Number(f.attributes?.WORKSPACE_ID ?? 0); if (!siruta || !Number.isFinite(ws) || ws <= 0) continue; - sirutaToWs.set(siruta, ws); const arr = wsToSirutas.get(ws); if (arr) arr.push(siruta); else wsToSirutas.set(ws, [siruta]); } console.log( - `[county-refresh] ${sirutaToWs.size} SIRUTAs, ${wsToSirutas.size} unique workspaces.`, + `[county-refresh] ${wsToSirutas.size} unique workspaces (counties).`, ); - // 3. Resolve WORKSPACE_ID → county name using known UAT mappings + // 3. Resolve county name for each workspace by fetching 1 immovable + // The response includes workspace: { nomenPk, name } = county name const wsToCounty = new Map(); - const allKnown = { ...COUNTY_SEATS, ...COUNTY_ALTS }; - for (const [siruta, county] of Object.entries(allKnown)) { - const ws = sirutaToWs.get(siruta); - if (ws != null && !wsToCounty.has(ws)) { - wsToCounty.set(ws, county); + for (const [ws, sirutas] of wsToSirutas) { + // Try up to 3 SIRUTAs per workspace (in case some have no immovables) + const candidates = sirutas.slice(0, 3); + for (const siruta of candidates) { + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const response: any = await client.fetchImmovableListByAdminUnit( + ws, + siruta, + 0, + 1, + false, // don't need CF filter + ); + + const items = response?.content ?? []; + if (items.length > 0) { + const wsName = items[0]?.workspace?.name; + if (typeof wsName === "string" && wsName.trim()) { + wsToCounty.set(ws, titleCase(wsName.trim())); + break; + } + } + } catch { + // This SIRUTA might have no immovables, try next + } } } console.log( - `[county-refresh] Resolved ${wsToCounty.size}/${wsToSirutas.size} workspaces via known UATs.`, + `[county-refresh] Resolved ${wsToCounty.size}/${wsToSirutas.size} county names from eTerra.`, ); - // Log unresolved workspaces for debugging - const unresolved: number[] = []; + // Log unresolved for (const ws of wsToSirutas.keys()) { - if (!wsToCounty.has(ws)) unresolved.push(ws); - } - if (unresolved.length > 0) { - // Find a sample SIRUTA for each unresolved workspace - for (const ws of unresolved) { - const sirutas = wsToSirutas.get(ws) ?? []; - const sampleSiruta = sirutas[0] ?? "?"; - // Look up name from GisUat + if (!wsToCounty.has(ws)) { + const sample = wsToSirutas.get(ws)?.[0] ?? "?"; const uat = await prisma.gisUat.findUnique({ - where: { siruta: sampleSiruta }, + where: { siruta: sample }, select: { name: true }, }); - console.log( - `[county-refresh] Unresolved workspace ${ws}: sample SIRUTA=${sampleSiruta} (${uat?.name ?? "?"})`, + console.warn( + `[county-refresh] Unresolved workspace ${ws}: sample ${sample} (${uat?.name ?? "?"})`, ); } } @@ -188,7 +133,6 @@ export async function refreshCountyData(client: EterraClient): Promise { const county = wsToCounty.get(ws); if (!county) continue; - // Update in batches of 200 for (let i = 0; i < sirutas.length; i += 200) { const batch = sirutas.slice(i, i + 200); const result = await prisma.gisUat.updateMany({