Files
ArchiTools/src/app/api/eterra/session/county-refresh.ts
T
AI Assistant f6781ab851 feat(parcel-sync): store UAT geometries from LIMITE_UAT in local DB
- 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>
2026-03-22 23:14:52 +02:00

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.`);
}