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:
@@ -391,6 +391,14 @@ export function ParcelSyncModule() {
|
||||
withGeometry: number;
|
||||
remoteGisCount: number;
|
||||
noGeomCount: number;
|
||||
qualityBreakdown: {
|
||||
withCadRef: number;
|
||||
withPaperCad: number;
|
||||
withPaperCf: number;
|
||||
withArea: number;
|
||||
useful: number;
|
||||
empty: number;
|
||||
};
|
||||
localDbTotal: number;
|
||||
localDbWithGeom: number;
|
||||
localDbNoGeom: number;
|
||||
@@ -693,11 +701,20 @@ export function ParcelSyncModule() {
|
||||
setNoGeomScanning(true);
|
||||
setNoGeomScan(null);
|
||||
setNoGeomScanSiruta(s);
|
||||
const emptyQuality = {
|
||||
withCadRef: 0,
|
||||
withPaperCad: 0,
|
||||
withPaperCf: 0,
|
||||
withArea: 0,
|
||||
useful: 0,
|
||||
empty: 0,
|
||||
};
|
||||
const emptyResult = {
|
||||
totalImmovables: 0,
|
||||
withGeometry: 0,
|
||||
remoteGisCount: 0,
|
||||
noGeomCount: 0,
|
||||
qualityBreakdown: emptyQuality,
|
||||
localDbTotal: 0,
|
||||
localDbWithGeom: 0,
|
||||
localDbNoGeom: 0,
|
||||
@@ -719,11 +736,20 @@ export function ParcelSyncModule() {
|
||||
console.warn("[no-geom-scan]", data.error);
|
||||
setNoGeomScan(emptyResult);
|
||||
} else {
|
||||
const qb = (data.qualityBreakdown ?? {}) as Record<string, unknown>;
|
||||
setNoGeomScan({
|
||||
totalImmovables: Number(data.totalImmovables ?? 0),
|
||||
withGeometry: Number(data.withGeometry ?? 0),
|
||||
remoteGisCount: Number(data.remoteGisCount ?? 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),
|
||||
localDbWithGeom: Number(data.localDbWithGeom ?? 0),
|
||||
localDbNoGeom: Number(data.localDbNoGeom ?? 0),
|
||||
@@ -2612,6 +2638,70 @@ export function ParcelSyncModule() {
|
||||
Include și parcelele fără geometrie la export
|
||||
</span>
|
||||
</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 && (
|
||||
<p className="text-[11px] text-muted-foreground ml-7">
|
||||
Vor fi importate în DB și incluse în CSV + Magic GPKG
|
||||
|
||||
@@ -91,6 +91,22 @@ const normalizeId = (value: unknown) => {
|
||||
const normalizeCadRef = (value: unknown) =>
|
||||
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 = {
|
||||
totalImmovables: number;
|
||||
/** 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 */
|
||||
remoteGisCount: number;
|
||||
noGeomCount: number;
|
||||
/** Quality breakdown of no-geometry items */
|
||||
qualityBreakdown: NoGeomQuality;
|
||||
/** Sample of immovable identifiers without geometry */
|
||||
samples: Array<{
|
||||
immovablePk: number;
|
||||
@@ -155,6 +173,14 @@ export async function scanNoGeometryParcels(
|
||||
withGeometry: 0,
|
||||
remoteGisCount: 0,
|
||||
noGeomCount: 0,
|
||||
qualityBreakdown: {
|
||||
withCadRef: 0,
|
||||
withPaperCad: 0,
|
||||
withPaperCf: 0,
|
||||
withArea: 0,
|
||||
useful: 0,
|
||||
empty: 0,
|
||||
},
|
||||
samples: [],
|
||||
localDbTotal: 0,
|
||||
localDbWithGeom: 0,
|
||||
@@ -287,11 +313,48 @@ export async function scanNoGeometryParcels(
|
||||
// withGeometry = immovables that MATCHED a GIS feature (always adds up)
|
||||
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 {
|
||||
totalImmovables: allImmovables.length,
|
||||
withGeometry: matchedCount,
|
||||
remoteGisCount: remoteFeatures.length,
|
||||
noGeomCount: noGeomItems.length,
|
||||
qualityBreakdown: {
|
||||
withCadRef: qWithCadRef,
|
||||
withPaperCad: qWithPaperCad,
|
||||
withPaperCf: qWithPaperCf,
|
||||
withArea: qWithArea,
|
||||
useful: qUseful,
|
||||
empty: qEmpty,
|
||||
},
|
||||
samples: noGeomItems.slice(0, 20),
|
||||
localDbTotal: localTotal,
|
||||
localDbWithGeom: localWithGeom,
|
||||
|
||||
Reference in New Issue
Block a user