fix(parcel-sync): use verified WORKSPACE_ID → county mapping from eTerra

LIMITE_UAT provides SIRUTA + WORKSPACE_ID for all 3186 UATs across 42
workspaces. eTerra nomenclature APIs all return 404, and immovable list
returns empty for small communes. Use verified workspace→county mapping
derived from eTerra data (cross-referenced sample UATs + DB confirmations).
Logs unknown workspaces if eTerra ever adds new ones.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-22 22:52:46 +02:00
parent 61a44525bf
commit 7b10f1e533
+66 -53
View File
@@ -1,28 +1,77 @@
/**
* County refresh — populates GisUat.county from eTerra.
* 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 (100% from eTerra, zero hardcoding):
* Strategy:
* 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
* 2. Map WORKSPACE_ID → county name. eTerra uses fixed workspace IDs
* for Romania's 42 counties — these are stable infrastructure identifiers
* (Romania has had 41 counties + București since 1997).
* 3. Batch-update GisUat.county + workspacePk
*
* If a new workspace appears (eTerra adds one), it's logged for manual
* investigation. The mapping is verified against known UATs in each county.
*/
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";
function titleCase(s: string): string {
return s
.toLowerCase()
.replace(/(?:^|\s)\S/g, (ch) => ch.toUpperCase());
}
/**
* eTerra WORKSPACE_ID → Romanian county name.
*
* Verified by cross-referencing LIMITE_UAT sample UATs:
* ws 10 → GÂRBOVA (Alba), ws 29 → BIRCHIȘ (Arad), etc.
* Plus confirmed from DB: ws 65 → BISTRIȚA, ws 127 → CLUJ-NAPOCA,
* ws 207 → BĂNIȚA (HD), ws 378 → HUȘI (VS), ws 396 → BROȘTENI (VN).
*/
const WORKSPACE_TO_COUNTY: Record<number, string> = {
10: "Alba",
29: "Arad",
38: "Argeș",
47: "Bacău",
56: "Bihor",
65: "Bistrița-Năsăud",
74: "Botoșani",
83: "Brașov",
92: "Brăila",
109: "Buzău",
118: "Caraș-Severin",
127: "Cluj",
136: "Constanța",
145: "Covasna",
154: "Dâmbovița",
163: "Dolj",
172: "Galați",
181: "Gorj",
190: "Harghita",
207: "Hunedoara",
216: "Ialomița",
225: "Iași",
234: "Ilfov",
243: "Maramureș",
252: "Mehedinți",
261: "Mureș",
270: "Neamț",
289: "Olt",
298: "Prahova",
305: "Satu Mare",
314: "Sălaj",
323: "Sibiu",
332: "Suceava",
341: "Teleorman",
350: "Timiș",
369: "Tulcea",
378: "Vaslui",
387: "Vâlcea",
396: "Vrancea",
403: "București",
519: "Călărași",
528: "Giurgiu",
};
export async function refreshCountyData(client: EterraClient): Promise<void> {
// Check if refresh is needed
@@ -57,7 +106,7 @@ export async function refreshCountyData(client: EterraClient): Promise<void> {
console.log(`[county-refresh] LIMITE_UAT: ${features.length} features.`);
if (features.length === 0) return;
// 2. Group SIRUTAs by WORKSPACE_ID
// 2. Group SIRUTAs by WORKSPACE_ID and resolve county name
const wsToSirutas = new Map<number, string[]>();
for (const f of features) {
@@ -76,52 +125,16 @@ export async function refreshCountyData(client: EterraClient): Promise<void> {
`[county-refresh] ${wsToSirutas.size} unique workspaces (counties).`,
);
// 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>();
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} county names from eTerra.`,
);
// Log unresolved
// 3. Log any unknown workspaces (new ones added by eTerra)
for (const ws of wsToSirutas.keys()) {
if (!wsToCounty.has(ws)) {
if (!(ws in WORKSPACE_TO_COUNTY)) {
const sample = wsToSirutas.get(ws)?.[0] ?? "?";
const uat = await prisma.gisUat.findUnique({
where: { siruta: sample },
select: { name: true },
});
console.warn(
`[county-refresh] Unresolved workspace ${ws}: sample ${sample} (${uat?.name ?? "?"})`,
`[county-refresh] Unknown workspace ${ws}: sample ${sample} (${uat?.name ?? "?"}). Add to WORKSPACE_TO_COUNTY mapping.`,
);
}
}
@@ -130,7 +143,7 @@ export async function refreshCountyData(client: EterraClient): Promise<void> {
let updated = 0;
for (const [ws, sirutas] of wsToSirutas) {
const county = wsToCounty.get(ws);
const county = WORKSPACE_TO_COUNTY[ws];
if (!county) continue;
for (let i = 0; i < sirutas.length; i += 200) {