fix(parcel-sync): resolve county names 100% from eTerra, zero hardcoding
LIMITE_UAT gives SIRUTA + WORKSPACE_ID for all 3186 UATs. For each of the 42 unique workspaces, fetch 1 immovable via fetchImmovableListBy AdminUnit — the response includes workspace.name = county name. No static mappings, no nomenclature endpoints (they 404). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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<string, string> = {
|
||||
// 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<string, string> = {
|
||||
"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<void> {
|
||||
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<void> {
|
||||
});
|
||||
|
||||
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<string, number>();
|
||||
// 2. Group SIRUTAs by WORKSPACE_ID
|
||||
const wsToSirutas = new Map<number, string[]>();
|
||||
|
||||
for (const f of features) {
|
||||
@@ -135,48 +67,61 @@ export async function refreshCountyData(client: EterraClient): Promise<void> {
|
||||
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<number, string>();
|
||||
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<void> {
|
||||
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({
|
||||
|
||||
Reference in New Issue
Block a user