/** * County refresh — populates GisUat.county from eTerra LIMITE_UAT layer. * * 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. */ 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() .replace(/(?:^|\s)\S/g, (ch) => ch.toUpperCase()); } export async function refreshCountyData(client: EterraClient): Promise { // Check if refresh is needed const [total, withCounty] = await Promise.all([ prisma.gisUat.count(), prisma.gisUat.count({ where: { county: { not: null } } }), ]); if (total === 0) return; if (withCounty > total * 0.5) { console.log( `[county-refresh] ${withCounty}/${total} already have county, skipping.`, ); return; } 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"); return; } const features = await client.fetchAllLayerByWhere(limiteUat, "1=1", { outFields: "ADMIN_UNIT_ID,WORKSPACE_ID", returnGeometry: false, pageSize: 1000, }); console.log(`[county-refresh] LIMITE_UAT: ${features.length} features.`); // 2. Build SIRUTA → WORKSPACE_ID map, and collect unique workspaces const sirutaToWs = new Map(); const wsToSirutas = new Map(); for (const f of features) { const siruta = String(f.attributes?.ADMIN_UNIT_ID ?? "") .trim() .replace(/\.0$/, ""); 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.`, ); // 3. Resolve WORKSPACE_ID → county name using known UAT mappings 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); } } console.log( `[county-refresh] Resolved ${wsToCounty.size}/${wsToSirutas.size} workspaces via known UATs.`, ); // Log unresolved workspaces for debugging const unresolved: number[] = []; 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 const uat = await prisma.gisUat.findUnique({ where: { siruta: sampleSiruta }, select: { name: true }, }); console.log( `[county-refresh] Unresolved workspace ${ws}: sample SIRUTA=${sampleSiruta} (${uat?.name ?? "?"})`, ); } } // 4. Batch update GisUat records let updated = 0; for (const [ws, sirutas] of wsToSirutas) { 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({ where: { siruta: { in: batch } }, data: { county, workspacePk: ws }, }); updated += result.count; } } console.log(`[county-refresh] Done: ${updated}/${total} updated.`); }