f6781ab851
- Add geometry (Json), areaValue (Float), lastUpdatedDtm (String) to GisUat model for local caching of UAT boundaries - County refresh now fetches LIMITE_UAT with returnGeometry=true and stores EsriGeometry rings per UAT in EPSG:3844 - Uses LAST_UPDATED_DTM from eTerra for future incremental sync - Skips geometry fetch if >50% already have geometry stored Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
177 lines
5.1 KiB
TypeScript
177 lines
5.1 KiB
TypeScript
/**
|
|
* County & geometry refresh — populates GisUat.county + geometry
|
|
* 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 WITH geometry →
|
|
* get ADMIN_UNIT_ID, WORKSPACE_ID, AREA_VALUE, LAST_UPDATED_DTM + rings
|
|
* 2. Map WORKSPACE_ID → county name via verified mapping
|
|
* 3. Batch-update GisUat: county, workspacePk, geometry, areaValue, lastUpdatedDtm
|
|
* 4. On subsequent runs: skip UATs where lastUpdatedDtm hasn't changed
|
|
*/
|
|
|
|
import { Prisma } from "@prisma/client";
|
|
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";
|
|
|
|
/**
|
|
* eTerra WORKSPACE_ID → Romanian county name.
|
|
*
|
|
* Verified by cross-referencing LIMITE_UAT sample UATs + DB confirmations.
|
|
*/
|
|
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> {
|
|
const total = await prisma.gisUat.count();
|
|
if (total === 0) return;
|
|
|
|
// Check how many are missing county OR geometry
|
|
const [withCounty, withGeometry] = await Promise.all([
|
|
prisma.gisUat.count({ where: { county: { not: null } } }),
|
|
prisma.gisUat.count({
|
|
where: { geometry: { not: Prisma.AnyNull } },
|
|
}),
|
|
]);
|
|
|
|
const needsCounty = withCounty < total * 0.5;
|
|
const needsGeometry = withGeometry < total * 0.5;
|
|
|
|
if (!needsCounty && !needsGeometry) {
|
|
console.log(
|
|
`[county-refresh] ${withCounty}/${total} counties, ${withGeometry}/${total} geometries — skipping.`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
console.log(
|
|
`[county-refresh] Starting: ${withCounty}/${total} counties, ${withGeometry}/${total} geometries.`,
|
|
);
|
|
|
|
// 1. Query LIMITE_UAT — with geometry if needed, without if only county
|
|
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,AREA_VALUE,LAST_UPDATED_DTM",
|
|
returnGeometry: needsGeometry,
|
|
pageSize: 1000,
|
|
});
|
|
|
|
console.log(
|
|
`[county-refresh] LIMITE_UAT: ${features.length} features` +
|
|
`${needsGeometry ? " (with geometry)" : ""}.`,
|
|
);
|
|
if (features.length === 0) return;
|
|
|
|
// 2. Log unknown workspaces
|
|
const seenWs = new Set<number>();
|
|
for (const f of features) {
|
|
const ws = Number(f.attributes?.WORKSPACE_ID ?? 0);
|
|
if (ws > 0 && !(ws in WORKSPACE_TO_COUNTY) && !seenWs.has(ws)) {
|
|
seenWs.add(ws);
|
|
const siruta = String(f.attributes?.ADMIN_UNIT_ID ?? "").replace(/\.0$/, "");
|
|
console.warn(
|
|
`[county-refresh] Unknown workspace ${ws} (SIRUTA ${siruta}). Add to mapping.`,
|
|
);
|
|
}
|
|
}
|
|
|
|
// 3. Upsert each UAT with county, workspacePk, geometry, area, lastUpdatedDtm
|
|
let updated = 0;
|
|
const BATCH = 50;
|
|
|
|
for (let i = 0; i < features.length; i += BATCH) {
|
|
const batch = features.slice(i, i + BATCH);
|
|
const ops = [];
|
|
|
|
for (const f of batch) {
|
|
const siruta = String(f.attributes?.ADMIN_UNIT_ID ?? "")
|
|
.trim()
|
|
.replace(/\.0$/, "");
|
|
const ws = Number(f.attributes?.WORKSPACE_ID ?? 0);
|
|
if (!siruta || ws <= 0) continue;
|
|
|
|
const county = WORKSPACE_TO_COUNTY[ws];
|
|
const areaValue = Number(f.attributes?.AREA_VALUE ?? 0) || null;
|
|
const lastUpdatedDtm = f.attributes?.LAST_UPDATED_DTM != null
|
|
? String(f.attributes.LAST_UPDATED_DTM)
|
|
: null;
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const geom = (f as any).geometry ?? null;
|
|
|
|
const data: Prisma.GisUatUpdateInput = {
|
|
workspacePk: ws,
|
|
...(county ? { county } : {}),
|
|
...(areaValue ? { areaValue } : {}),
|
|
...(lastUpdatedDtm ? { lastUpdatedDtm } : {}),
|
|
...(geom ? { geometry: geom as Prisma.InputJsonValue } : {}),
|
|
};
|
|
|
|
ops.push(
|
|
prisma.gisUat.updateMany({
|
|
where: { siruta },
|
|
data,
|
|
}),
|
|
);
|
|
}
|
|
|
|
if (ops.length > 0) {
|
|
const results = await prisma.$transaction(ops);
|
|
for (const r of results) updated += r.count;
|
|
}
|
|
}
|
|
|
|
console.log(`[county-refresh] Done: ${updated}/${total} updated.`);
|
|
}
|