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
|
* Called with an already-authenticated EterraClient (fire-and-forget
|
||||||
* after login), so there's no session expiry risk.
|
* 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) →
|
* 1. Query LIMITE_UAT for all features (no geometry) →
|
||||||
* get ADMIN_UNIT_ID (SIRUTA) + WORKSPACE_ID per UAT
|
* get ADMIN_UNIT_ID (SIRUTA) + WORKSPACE_ID per UAT
|
||||||
* 2. Group by WORKSPACE_ID → 42 unique workspaces (= 42 counties)
|
* 2. Map WORKSPACE_ID → county name. eTerra uses fixed workspace IDs
|
||||||
* 3. For each workspace, pick one SIRUTA and call
|
* for Romania's 42 counties — these are stable infrastructure identifiers
|
||||||
* fetchImmovableListByAdminUnit(workspaceId, siruta, page=0, size=1)
|
* (Romania has had 41 counties + București since 1997).
|
||||||
* → response includes workspace.name = county name
|
* 3. Batch-update GisUat.county + workspacePk
|
||||||
* 4. 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 { prisma } from "@/core/storage/prisma";
|
||||||
import type { EterraClient } from "@/modules/parcel-sync/services/eterra-client";
|
import type { EterraClient } from "@/modules/parcel-sync/services/eterra-client";
|
||||||
import { findLayerById } from "@/modules/parcel-sync/services/eterra-layers";
|
import { findLayerById } from "@/modules/parcel-sync/services/eterra-layers";
|
||||||
|
|
||||||
function titleCase(s: string): string {
|
/**
|
||||||
return s
|
* eTerra WORKSPACE_ID → Romanian county name.
|
||||||
.toLowerCase()
|
*
|
||||||
.replace(/(?:^|\s)\S/g, (ch) => ch.toUpperCase());
|
* 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> {
|
export async function refreshCountyData(client: EterraClient): Promise<void> {
|
||||||
// Check if refresh is needed
|
// 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.`);
|
console.log(`[county-refresh] LIMITE_UAT: ${features.length} features.`);
|
||||||
if (features.length === 0) return;
|
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[]>();
|
const wsToSirutas = new Map<number, string[]>();
|
||||||
|
|
||||||
for (const f of features) {
|
for (const f of features) {
|
||||||
@@ -76,52 +125,16 @@ export async function refreshCountyData(client: EterraClient): Promise<void> {
|
|||||||
`[county-refresh] ${wsToSirutas.size} unique workspaces (counties).`,
|
`[county-refresh] ${wsToSirutas.size} unique workspaces (counties).`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3. Resolve county name for each workspace by fetching 1 immovable
|
// 3. Log any unknown workspaces (new ones added by eTerra)
|
||||||
// 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
|
|
||||||
for (const ws of wsToSirutas.keys()) {
|
for (const ws of wsToSirutas.keys()) {
|
||||||
if (!wsToCounty.has(ws)) {
|
if (!(ws in WORKSPACE_TO_COUNTY)) {
|
||||||
const sample = wsToSirutas.get(ws)?.[0] ?? "?";
|
const sample = wsToSirutas.get(ws)?.[0] ?? "?";
|
||||||
const uat = await prisma.gisUat.findUnique({
|
const uat = await prisma.gisUat.findUnique({
|
||||||
where: { siruta: sample },
|
where: { siruta: sample },
|
||||||
select: { name: true },
|
select: { name: true },
|
||||||
});
|
});
|
||||||
console.warn(
|
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;
|
let updated = 0;
|
||||||
|
|
||||||
for (const [ws, sirutas] of wsToSirutas) {
|
for (const [ws, sirutas] of wsToSirutas) {
|
||||||
const county = wsToCounty.get(ws);
|
const county = WORKSPACE_TO_COUNTY[ws];
|
||||||
if (!county) continue;
|
if (!county) continue;
|
||||||
|
|
||||||
for (let i = 0; i < sirutas.length; i += 200) {
|
for (let i = 0; i < sirutas.length; i += 200) {
|
||||||
|
|||||||
Reference in New Issue
Block a user