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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user