feat: quality analysis for no-geom parcels + raport_calitate.txt
Scan phase: - qualityBreakdown on NoGeomScanResult: withCadRef, withPaperCad, withPaperCf, withArea, useful vs empty counts - UI scan card shows quality grid before deciding to export Export phase: - Comprehensive enrichment quality analysis: owners, CF, address, area, category, building — split by with-geom vs no-geom - raport_calitate.txt in ZIP: human-readable Romanian report with per-category breakdowns and percentage stats - export_report.json includes full qualityAnalysis object - Progress completion note shows quality summary inline
This commit is contained in:
@@ -540,6 +540,84 @@ export async function POST(req: Request) {
|
|||||||
zip.file("terenuri.gpkg", terenuriGpkg);
|
zip.file("terenuri.gpkg", terenuriGpkg);
|
||||||
zip.file("cladiri.gpkg", cladiriGpkg);
|
zip.file("cladiri.gpkg", cladiriGpkg);
|
||||||
|
|
||||||
|
// ── Comprehensive quality analysis ──
|
||||||
|
const withGeomRecords = dbTerenuri.filter(
|
||||||
|
(r) =>
|
||||||
|
(r as unknown as { geometrySource: string | null }).geometrySource !==
|
||||||
|
"NO_GEOMETRY",
|
||||||
|
);
|
||||||
|
const noGeomRecords = dbTerenuri.filter(
|
||||||
|
(r) =>
|
||||||
|
(r as unknown as { geometrySource: string | null }).geometrySource ===
|
||||||
|
"NO_GEOMETRY",
|
||||||
|
);
|
||||||
|
|
||||||
|
const analyzeRecords = (records: typeof dbTerenuri) => {
|
||||||
|
let enriched = 0;
|
||||||
|
let withOwners = 0;
|
||||||
|
let withOldOwners = 0;
|
||||||
|
let withCF = 0;
|
||||||
|
let withAddress = 0;
|
||||||
|
let withArea = 0;
|
||||||
|
let withCategory = 0;
|
||||||
|
let withBuilding = 0;
|
||||||
|
let complete = 0;
|
||||||
|
let partial = 0;
|
||||||
|
let empty = 0;
|
||||||
|
|
||||||
|
for (const r of records) {
|
||||||
|
const e = r.enrichment as Record<string, unknown> | null;
|
||||||
|
if (!e) {
|
||||||
|
empty++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
enriched++;
|
||||||
|
const hasOwners = !!e.PROPRIETARI && e.PROPRIETARI !== "-";
|
||||||
|
const hasOldOwners =
|
||||||
|
!!e.PROPRIETARI_VECHI && String(e.PROPRIETARI_VECHI).trim() !== "";
|
||||||
|
const hasCF = !!e.NR_CF && e.NR_CF !== "-";
|
||||||
|
const hasAddr = !!e.ADRESA && e.ADRESA !== "-";
|
||||||
|
const hasArea =
|
||||||
|
e.SUPRAFATA_2D != null &&
|
||||||
|
e.SUPRAFATA_2D !== "" &&
|
||||||
|
Number(e.SUPRAFATA_2D) > 0;
|
||||||
|
const hasCat = !!e.CATEGORIE_FOLOSINTA && e.CATEGORIE_FOLOSINTA !== "-";
|
||||||
|
const hasBuild = Number(e.HAS_BUILDING ?? 0) === 1;
|
||||||
|
|
||||||
|
if (hasOwners) withOwners++;
|
||||||
|
if (hasOldOwners) withOldOwners++;
|
||||||
|
if (hasCF) withCF++;
|
||||||
|
if (hasAddr) withAddress++;
|
||||||
|
if (hasArea) withArea++;
|
||||||
|
if (hasCat) withCategory++;
|
||||||
|
if (hasBuild) withBuilding++;
|
||||||
|
|
||||||
|
// "Complete" = has at least owners + CF + area
|
||||||
|
if (hasOwners && hasCF && hasArea) complete++;
|
||||||
|
else if (hasOwners || hasCF || hasAddr || hasArea || hasCat) partial++;
|
||||||
|
else empty++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: records.length,
|
||||||
|
enriched,
|
||||||
|
withOwners,
|
||||||
|
withOldOwners,
|
||||||
|
withCF,
|
||||||
|
withAddress,
|
||||||
|
withArea,
|
||||||
|
withCategory,
|
||||||
|
withBuilding,
|
||||||
|
complete,
|
||||||
|
partial,
|
||||||
|
empty,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const qualityAll = analyzeRecords(dbTerenuri);
|
||||||
|
const qualityGeom = analyzeRecords(withGeomRecords);
|
||||||
|
const qualityNoGeom = analyzeRecords(noGeomRecords);
|
||||||
|
|
||||||
const report: Record<string, unknown> = {
|
const report: Record<string, unknown> = {
|
||||||
siruta: validated.siruta,
|
siruta: validated.siruta,
|
||||||
generatedAt: new Date().toISOString(),
|
generatedAt: new Date().toISOString(),
|
||||||
@@ -547,11 +625,7 @@ export async function POST(req: Request) {
|
|||||||
terenuri: {
|
terenuri: {
|
||||||
count: terenuriGeoFeatures.length,
|
count: terenuriGeoFeatures.length,
|
||||||
totalInDb: dbTerenuri.length,
|
totalInDb: dbTerenuri.length,
|
||||||
noGeometryCount: dbTerenuri.filter(
|
noGeometryCount: noGeomRecords.length,
|
||||||
(r) =>
|
|
||||||
(r as unknown as { geometrySource: string | null })
|
|
||||||
.geometrySource === "NO_GEOMETRY",
|
|
||||||
).length,
|
|
||||||
},
|
},
|
||||||
cladiri: { count: cladiriGeoFeatures.length },
|
cladiri: { count: cladiriGeoFeatures.length },
|
||||||
syncSkipped: {
|
syncSkipped: {
|
||||||
@@ -560,6 +634,11 @@ export async function POST(req: Request) {
|
|||||||
},
|
},
|
||||||
includeNoGeometry: hasNoGeom,
|
includeNoGeometry: hasNoGeom,
|
||||||
noGeomImported,
|
noGeomImported,
|
||||||
|
qualityAnalysis: {
|
||||||
|
all: qualityAll,
|
||||||
|
withGeometry: qualityGeom,
|
||||||
|
noGeometry: qualityNoGeom,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (validated.mode === "magic" && magicGpkg && csvContent) {
|
if (validated.mode === "magic" && magicGpkg && csvContent) {
|
||||||
@@ -570,6 +649,89 @@ export async function POST(req: Request) {
|
|||||||
hasBuildingCount,
|
hasBuildingCount,
|
||||||
legalBuildingCount,
|
legalBuildingCount,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Generate human-readable quality report (Romanian)
|
||||||
|
const pct = (n: number, total: number) =>
|
||||||
|
total > 0 ? `${((n / total) * 100).toFixed(1)}%` : "0%";
|
||||||
|
const fmt = (n: number) => n.toLocaleString("ro-RO");
|
||||||
|
|
||||||
|
const lines: string[] = [
|
||||||
|
`══════════════════════════════════════════════════════════`,
|
||||||
|
` RAPORT CALITATE DATE — UAT SIRUTA ${validated.siruta}`,
|
||||||
|
` Generat: ${new Date().toISOString().replace("T", " ").slice(0, 19)}`,
|
||||||
|
`══════════════════════════════════════════════════════════`,
|
||||||
|
``,
|
||||||
|
`REZUMAT GENERAL`,
|
||||||
|
`─────────────────────────────────────────────────────────`,
|
||||||
|
` Total parcele în baza de date: ${fmt(dbTerenuri.length)}`,
|
||||||
|
` • Cu geometrie (contur GIS): ${fmt(withGeomRecords.length)}`,
|
||||||
|
` • Fără geometrie (doar date): ${fmt(noGeomRecords.length)}`,
|
||||||
|
` Clădiri: ${fmt(cladiriGeoFeatures.length)}`,
|
||||||
|
``,
|
||||||
|
`CALITATE ÎMBOGĂȚIRE — TOATE PARCELELE (${fmt(qualityAll.total)})`,
|
||||||
|
`─────────────────────────────────────────────────────────`,
|
||||||
|
` Îmbogățite: ${fmt(qualityAll.enriched)} (${pct(qualityAll.enriched, qualityAll.total)})`,
|
||||||
|
` Cu proprietari: ${fmt(qualityAll.withOwners)} (${pct(qualityAll.withOwners, qualityAll.total)})`,
|
||||||
|
` Cu prop. vechi: ${fmt(qualityAll.withOldOwners)} (${pct(qualityAll.withOldOwners, qualityAll.total)})`,
|
||||||
|
` Cu nr. CF: ${fmt(qualityAll.withCF)} (${pct(qualityAll.withCF, qualityAll.total)})`,
|
||||||
|
` Cu adresă: ${fmt(qualityAll.withAddress)} (${pct(qualityAll.withAddress, qualityAll.total)})`,
|
||||||
|
` Cu suprafață: ${fmt(qualityAll.withArea)} (${pct(qualityAll.withArea, qualityAll.total)})`,
|
||||||
|
` Cu categorie fol.: ${fmt(qualityAll.withCategory)} (${pct(qualityAll.withCategory, qualityAll.total)})`,
|
||||||
|
` Cu clădire: ${fmt(qualityAll.withBuilding)} (${pct(qualityAll.withBuilding, qualityAll.total)})`,
|
||||||
|
` ────────────────`,
|
||||||
|
` Complete (prop+CF+sup): ${fmt(qualityAll.complete)} (${pct(qualityAll.complete, qualityAll.total)})`,
|
||||||
|
` Parțiale: ${fmt(qualityAll.partial)} (${pct(qualityAll.partial, qualityAll.total)})`,
|
||||||
|
` Goale (fără date): ${fmt(qualityAll.empty)} (${pct(qualityAll.empty, qualityAll.total)})`,
|
||||||
|
``,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (withGeomRecords.length > 0) {
|
||||||
|
lines.push(
|
||||||
|
`PARCELE CU GEOMETRIE (${fmt(qualityGeom.total)})`,
|
||||||
|
`─────────────────────────────────────────────────────────`,
|
||||||
|
` Îmbogățite: ${fmt(qualityGeom.enriched)} (${pct(qualityGeom.enriched, qualityGeom.total)})`,
|
||||||
|
` Cu proprietari: ${fmt(qualityGeom.withOwners)} (${pct(qualityGeom.withOwners, qualityGeom.total)})`,
|
||||||
|
` Cu nr. CF: ${fmt(qualityGeom.withCF)} (${pct(qualityGeom.withCF, qualityGeom.total)})`,
|
||||||
|
` Cu adresă: ${fmt(qualityGeom.withAddress)} (${pct(qualityGeom.withAddress, qualityGeom.total)})`,
|
||||||
|
` Cu suprafață: ${fmt(qualityGeom.withArea)} (${pct(qualityGeom.withArea, qualityGeom.total)})`,
|
||||||
|
` Cu categorie fol.: ${fmt(qualityGeom.withCategory)} (${pct(qualityGeom.withCategory, qualityGeom.total)})`,
|
||||||
|
` Complete: ${fmt(qualityGeom.complete)} (${pct(qualityGeom.complete, qualityGeom.total)})`,
|
||||||
|
` Parțiale: ${fmt(qualityGeom.partial)} (${pct(qualityGeom.partial, qualityGeom.total)})`,
|
||||||
|
` Goale: ${fmt(qualityGeom.empty)} (${pct(qualityGeom.empty, qualityGeom.total)})`,
|
||||||
|
``,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noGeomRecords.length > 0) {
|
||||||
|
lines.push(
|
||||||
|
`PARCELE FĂRĂ GEOMETRIE (${fmt(qualityNoGeom.total)})`,
|
||||||
|
`─────────────────────────────────────────────────────────`,
|
||||||
|
` Îmbogățite: ${fmt(qualityNoGeom.enriched)} (${pct(qualityNoGeom.enriched, qualityNoGeom.total)})`,
|
||||||
|
` Cu proprietari: ${fmt(qualityNoGeom.withOwners)} (${pct(qualityNoGeom.withOwners, qualityNoGeom.total)})`,
|
||||||
|
` Cu nr. CF: ${fmt(qualityNoGeom.withCF)} (${pct(qualityNoGeom.withCF, qualityNoGeom.total)})`,
|
||||||
|
` Cu adresă: ${fmt(qualityNoGeom.withAddress)} (${pct(qualityNoGeom.withAddress, qualityNoGeom.total)})`,
|
||||||
|
` Cu suprafață: ${fmt(qualityNoGeom.withArea)} (${pct(qualityNoGeom.withArea, qualityNoGeom.total)})`,
|
||||||
|
` Cu categorie fol.: ${fmt(qualityNoGeom.withCategory)} (${pct(qualityNoGeom.withCategory, qualityNoGeom.total)})`,
|
||||||
|
` Complete: ${fmt(qualityNoGeom.complete)} (${pct(qualityNoGeom.complete, qualityNoGeom.total)})`,
|
||||||
|
` Parțiale: ${fmt(qualityNoGeom.partial)} (${pct(qualityNoGeom.partial, qualityNoGeom.total)})`,
|
||||||
|
` Goale: ${fmt(qualityNoGeom.empty)} (${pct(qualityNoGeom.empty, qualityNoGeom.total)})`,
|
||||||
|
``,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(
|
||||||
|
`NOTE`,
|
||||||
|
`─────────────────────────────────────────────────────────`,
|
||||||
|
` • "Complete" = are proprietari + nr. CF + suprafață`,
|
||||||
|
` • "Parțiale" = are cel puțin un câmp util`,
|
||||||
|
` • "Goale" = niciun câmp de îmbogățire completat`,
|
||||||
|
` • Parcelele fără geometrie provin din lista de imobile`,
|
||||||
|
` eTerra și nu au contur desenat în layerul GIS.`,
|
||||||
|
` • Datele sunt extrase din ANCPI eTerra la data raportului.`,
|
||||||
|
`══════════════════════════════════════════════════════════`,
|
||||||
|
);
|
||||||
|
|
||||||
|
zip.file("raport_calitate.txt", lines.join("\n"));
|
||||||
}
|
}
|
||||||
zip.file("export_report.json", JSON.stringify(report, null, 2));
|
zip.file("export_report.json", JSON.stringify(report, null, 2));
|
||||||
|
|
||||||
@@ -580,11 +742,7 @@ export async function POST(req: Request) {
|
|||||||
finishPhase();
|
finishPhase();
|
||||||
|
|
||||||
/* Done */
|
/* Done */
|
||||||
const noGeomInDb = dbTerenuri.filter(
|
const noGeomInDb = noGeomRecords.length;
|
||||||
(r) =>
|
|
||||||
(r as unknown as { geometrySource: string | null }).geometrySource ===
|
|
||||||
"NO_GEOMETRY",
|
|
||||||
).length;
|
|
||||||
message = `Finalizat 100% · Terenuri ${terenuriGeoFeatures.length} · Clădiri ${cladiriGeoFeatures.length}`;
|
message = `Finalizat 100% · Terenuri ${terenuriGeoFeatures.length} · Clădiri ${cladiriGeoFeatures.length}`;
|
||||||
if (noGeomInDb > 0) {
|
if (noGeomInDb > 0) {
|
||||||
message += ` · Fără geometrie ${noGeomInDb}`;
|
message += ` · Fără geometrie ${noGeomInDb}`;
|
||||||
@@ -592,9 +750,21 @@ export async function POST(req: Request) {
|
|||||||
if (!terenuriNeedsSync && !cladiriNeedsSync) {
|
if (!terenuriNeedsSync && !cladiriNeedsSync) {
|
||||||
message += " (din cache local)";
|
message += " (din cache local)";
|
||||||
}
|
}
|
||||||
|
// Quality summary in note (visible in UI progress card)
|
||||||
|
if (validated.mode === "magic") {
|
||||||
|
const qParts: string[] = [];
|
||||||
|
qParts.push(`Complete: ${qualityAll.complete}/${qualityAll.total}`);
|
||||||
|
if (qualityAll.partial > 0)
|
||||||
|
qParts.push(`parțiale: ${qualityAll.partial}`);
|
||||||
|
if (qualityAll.empty > 0) qParts.push(`goale: ${qualityAll.empty}`);
|
||||||
|
qParts.push(`prop: ${qualityAll.withOwners}`);
|
||||||
|
qParts.push(`CF: ${qualityAll.withCF}`);
|
||||||
|
qParts.push(`sup: ${qualityAll.withArea}`);
|
||||||
|
note = `Calitate: ${qParts.join(" · ")} — vezi raport_calitate.txt în ZIP`;
|
||||||
|
}
|
||||||
status = "done";
|
status = "done";
|
||||||
phase = "Finalizat";
|
phase = "Finalizat";
|
||||||
note = undefined;
|
// note already set with quality summary above (or undefined for base mode)
|
||||||
pushProgress();
|
pushProgress();
|
||||||
scheduleClear(jobId);
|
scheduleClear(jobId);
|
||||||
|
|
||||||
|
|||||||
@@ -391,6 +391,14 @@ export function ParcelSyncModule() {
|
|||||||
withGeometry: number;
|
withGeometry: number;
|
||||||
remoteGisCount: number;
|
remoteGisCount: number;
|
||||||
noGeomCount: number;
|
noGeomCount: number;
|
||||||
|
qualityBreakdown: {
|
||||||
|
withCadRef: number;
|
||||||
|
withPaperCad: number;
|
||||||
|
withPaperCf: number;
|
||||||
|
withArea: number;
|
||||||
|
useful: number;
|
||||||
|
empty: number;
|
||||||
|
};
|
||||||
localDbTotal: number;
|
localDbTotal: number;
|
||||||
localDbWithGeom: number;
|
localDbWithGeom: number;
|
||||||
localDbNoGeom: number;
|
localDbNoGeom: number;
|
||||||
@@ -693,11 +701,20 @@ export function ParcelSyncModule() {
|
|||||||
setNoGeomScanning(true);
|
setNoGeomScanning(true);
|
||||||
setNoGeomScan(null);
|
setNoGeomScan(null);
|
||||||
setNoGeomScanSiruta(s);
|
setNoGeomScanSiruta(s);
|
||||||
|
const emptyQuality = {
|
||||||
|
withCadRef: 0,
|
||||||
|
withPaperCad: 0,
|
||||||
|
withPaperCf: 0,
|
||||||
|
withArea: 0,
|
||||||
|
useful: 0,
|
||||||
|
empty: 0,
|
||||||
|
};
|
||||||
const emptyResult = {
|
const emptyResult = {
|
||||||
totalImmovables: 0,
|
totalImmovables: 0,
|
||||||
withGeometry: 0,
|
withGeometry: 0,
|
||||||
remoteGisCount: 0,
|
remoteGisCount: 0,
|
||||||
noGeomCount: 0,
|
noGeomCount: 0,
|
||||||
|
qualityBreakdown: emptyQuality,
|
||||||
localDbTotal: 0,
|
localDbTotal: 0,
|
||||||
localDbWithGeom: 0,
|
localDbWithGeom: 0,
|
||||||
localDbNoGeom: 0,
|
localDbNoGeom: 0,
|
||||||
@@ -719,11 +736,20 @@ export function ParcelSyncModule() {
|
|||||||
console.warn("[no-geom-scan]", data.error);
|
console.warn("[no-geom-scan]", data.error);
|
||||||
setNoGeomScan(emptyResult);
|
setNoGeomScan(emptyResult);
|
||||||
} else {
|
} else {
|
||||||
|
const qb = (data.qualityBreakdown ?? {}) as Record<string, unknown>;
|
||||||
setNoGeomScan({
|
setNoGeomScan({
|
||||||
totalImmovables: Number(data.totalImmovables ?? 0),
|
totalImmovables: Number(data.totalImmovables ?? 0),
|
||||||
withGeometry: Number(data.withGeometry ?? 0),
|
withGeometry: Number(data.withGeometry ?? 0),
|
||||||
remoteGisCount: Number(data.remoteGisCount ?? 0),
|
remoteGisCount: Number(data.remoteGisCount ?? 0),
|
||||||
noGeomCount: Number(data.noGeomCount ?? 0),
|
noGeomCount: Number(data.noGeomCount ?? 0),
|
||||||
|
qualityBreakdown: {
|
||||||
|
withCadRef: Number(qb.withCadRef ?? 0),
|
||||||
|
withPaperCad: Number(qb.withPaperCad ?? 0),
|
||||||
|
withPaperCf: Number(qb.withPaperCf ?? 0),
|
||||||
|
withArea: Number(qb.withArea ?? 0),
|
||||||
|
useful: Number(qb.useful ?? 0),
|
||||||
|
empty: Number(qb.empty ?? 0),
|
||||||
|
},
|
||||||
localDbTotal: Number(data.localDbTotal ?? 0),
|
localDbTotal: Number(data.localDbTotal ?? 0),
|
||||||
localDbWithGeom: Number(data.localDbWithGeom ?? 0),
|
localDbWithGeom: Number(data.localDbWithGeom ?? 0),
|
||||||
localDbNoGeom: Number(data.localDbNoGeom ?? 0),
|
localDbNoGeom: Number(data.localDbNoGeom ?? 0),
|
||||||
@@ -2612,6 +2638,70 @@ export function ParcelSyncModule() {
|
|||||||
Include și parcelele fără geometrie la export
|
Include și parcelele fără geometrie la export
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
{/* Quality breakdown of no-geom items */}
|
||||||
|
{scanDone && noGeomScan.noGeomCount > 0 && (
|
||||||
|
<div className="ml-7 p-2 rounded-md bg-muted/40 space-y-1">
|
||||||
|
<p className="text-[11px] font-medium text-muted-foreground">
|
||||||
|
Calitate date (din{" "}
|
||||||
|
{noGeomScan.noGeomCount.toLocaleString("ro-RO")} fără
|
||||||
|
geometrie):
|
||||||
|
</p>
|
||||||
|
<div className="grid grid-cols-2 gap-x-4 gap-y-0.5 text-[11px] text-muted-foreground">
|
||||||
|
<span>
|
||||||
|
Cu nr. cadastral eTerra:{" "}
|
||||||
|
<span className="font-medium text-foreground">
|
||||||
|
{noGeomScan.qualityBreakdown.withCadRef.toLocaleString(
|
||||||
|
"ro-RO",
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
Cu nr. CF pe hârtie:{" "}
|
||||||
|
<span className="font-medium text-foreground">
|
||||||
|
{noGeomScan.qualityBreakdown.withPaperCf.toLocaleString(
|
||||||
|
"ro-RO",
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
Cu nr. cad. pe hârtie:{" "}
|
||||||
|
<span className="font-medium text-foreground">
|
||||||
|
{noGeomScan.qualityBreakdown.withPaperCad.toLocaleString(
|
||||||
|
"ro-RO",
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
Cu suprafață:{" "}
|
||||||
|
<span className="font-medium text-foreground">
|
||||||
|
{noGeomScan.qualityBreakdown.withArea.toLocaleString(
|
||||||
|
"ro-RO",
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 text-[11px] pt-0.5 border-t border-muted-foreground/10">
|
||||||
|
<span>
|
||||||
|
Utilizabile:{" "}
|
||||||
|
<span className="font-semibold text-emerald-600 dark:text-emerald-400">
|
||||||
|
{noGeomScan.qualityBreakdown.useful.toLocaleString(
|
||||||
|
"ro-RO",
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
{noGeomScan.qualityBreakdown.empty > 0 && (
|
||||||
|
<span>
|
||||||
|
Fără date identificare:{" "}
|
||||||
|
<span className="font-semibold text-rose-600 dark:text-rose-400">
|
||||||
|
{noGeomScan.qualityBreakdown.empty.toLocaleString(
|
||||||
|
"ro-RO",
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{includeNoGeom && (
|
{includeNoGeom && (
|
||||||
<p className="text-[11px] text-muted-foreground ml-7">
|
<p className="text-[11px] text-muted-foreground ml-7">
|
||||||
Vor fi importate în DB și incluse în CSV + Magic GPKG
|
Vor fi importate în DB și incluse în CSV + Magic GPKG
|
||||||
|
|||||||
@@ -91,6 +91,22 @@ const normalizeId = (value: unknown) => {
|
|||||||
const normalizeCadRef = (value: unknown) =>
|
const normalizeCadRef = (value: unknown) =>
|
||||||
normalizeId(value).replace(/\s+/g, "").toUpperCase();
|
normalizeId(value).replace(/\s+/g, "").toUpperCase();
|
||||||
|
|
||||||
|
/** Quality breakdown of no-geometry immovables from scan */
|
||||||
|
export type NoGeomQuality = {
|
||||||
|
/** Have electronic cadRef (identifierDetails non-empty) */
|
||||||
|
withCadRef: number;
|
||||||
|
/** Have paper cadastral number */
|
||||||
|
withPaperCad: number;
|
||||||
|
/** Have paper CF (carte funciară) number */
|
||||||
|
withPaperCf: number;
|
||||||
|
/** Have area > 0 */
|
||||||
|
withArea: number;
|
||||||
|
/** "Useful" = have cadRef OR (paperCad AND paperCf) */
|
||||||
|
useful: number;
|
||||||
|
/** No cadRef, no paperCad, no paperCf — likely unusable */
|
||||||
|
empty: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type NoGeomScanResult = {
|
export type NoGeomScanResult = {
|
||||||
totalImmovables: number;
|
totalImmovables: number;
|
||||||
/** Immovables that matched a remote GIS feature (have geometry) */
|
/** Immovables that matched a remote GIS feature (have geometry) */
|
||||||
@@ -98,6 +114,8 @@ export type NoGeomScanResult = {
|
|||||||
/** Total features in the remote ArcGIS TERENURI_ACTIVE layer */
|
/** Total features in the remote ArcGIS TERENURI_ACTIVE layer */
|
||||||
remoteGisCount: number;
|
remoteGisCount: number;
|
||||||
noGeomCount: number;
|
noGeomCount: number;
|
||||||
|
/** Quality breakdown of no-geometry items */
|
||||||
|
qualityBreakdown: NoGeomQuality;
|
||||||
/** Sample of immovable identifiers without geometry */
|
/** Sample of immovable identifiers without geometry */
|
||||||
samples: Array<{
|
samples: Array<{
|
||||||
immovablePk: number;
|
immovablePk: number;
|
||||||
@@ -155,6 +173,14 @@ export async function scanNoGeometryParcels(
|
|||||||
withGeometry: 0,
|
withGeometry: 0,
|
||||||
remoteGisCount: 0,
|
remoteGisCount: 0,
|
||||||
noGeomCount: 0,
|
noGeomCount: 0,
|
||||||
|
qualityBreakdown: {
|
||||||
|
withCadRef: 0,
|
||||||
|
withPaperCad: 0,
|
||||||
|
withPaperCf: 0,
|
||||||
|
withArea: 0,
|
||||||
|
useful: 0,
|
||||||
|
empty: 0,
|
||||||
|
},
|
||||||
samples: [],
|
samples: [],
|
||||||
localDbTotal: 0,
|
localDbTotal: 0,
|
||||||
localDbWithGeom: 0,
|
localDbWithGeom: 0,
|
||||||
@@ -287,11 +313,48 @@ export async function scanNoGeometryParcels(
|
|||||||
// withGeometry = immovables that MATCHED a GIS feature (always adds up)
|
// withGeometry = immovables that MATCHED a GIS feature (always adds up)
|
||||||
const matchedCount = allImmovables.length - noGeomItems.length;
|
const matchedCount = allImmovables.length - noGeomItems.length;
|
||||||
|
|
||||||
|
// Quality analysis of no-geom items
|
||||||
|
// Build a quick lookup for area data from the immovable list
|
||||||
|
const areaByPk = new Map<number, number>();
|
||||||
|
for (const item of allImmovables) {
|
||||||
|
const pk = Number(item.immovablePk ?? 0);
|
||||||
|
if (pk > 0 && typeof item.area === "number" && item.area > 0) {
|
||||||
|
areaByPk.set(pk, item.area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let qWithCadRef = 0;
|
||||||
|
let qWithPaperCad = 0;
|
||||||
|
let qWithPaperCf = 0;
|
||||||
|
let qWithArea = 0;
|
||||||
|
let qUseful = 0;
|
||||||
|
let qEmpty = 0;
|
||||||
|
for (const item of noGeomItems) {
|
||||||
|
const hasCad = !!item.identifierDetails?.trim();
|
||||||
|
const hasPaperCad = !!item.paperCadNo?.trim();
|
||||||
|
const hasPaperCf = !!item.paperCfNo?.trim();
|
||||||
|
const hasArea = areaByPk.has(item.immovablePk);
|
||||||
|
if (hasCad) qWithCadRef++;
|
||||||
|
if (hasPaperCad) qWithPaperCad++;
|
||||||
|
if (hasPaperCf) qWithPaperCf++;
|
||||||
|
if (hasArea) qWithArea++;
|
||||||
|
if (hasCad || (hasPaperCad && hasPaperCf)) qUseful++;
|
||||||
|
else qEmpty++;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalImmovables: allImmovables.length,
|
totalImmovables: allImmovables.length,
|
||||||
withGeometry: matchedCount,
|
withGeometry: matchedCount,
|
||||||
remoteGisCount: remoteFeatures.length,
|
remoteGisCount: remoteFeatures.length,
|
||||||
noGeomCount: noGeomItems.length,
|
noGeomCount: noGeomItems.length,
|
||||||
|
qualityBreakdown: {
|
||||||
|
withCadRef: qWithCadRef,
|
||||||
|
withPaperCad: qWithPaperCad,
|
||||||
|
withPaperCf: qWithPaperCf,
|
||||||
|
withArea: qWithArea,
|
||||||
|
useful: qUseful,
|
||||||
|
empty: qEmpty,
|
||||||
|
},
|
||||||
samples: noGeomItems.slice(0, 20),
|
samples: noGeomItems.slice(0, 20),
|
||||||
localDbTotal: localTotal,
|
localDbTotal: localTotal,
|
||||||
localDbWithGeom: localWithGeom,
|
localDbWithGeom: localWithGeom,
|
||||||
|
|||||||
Reference in New Issue
Block a user