fix: scan numbers always add up, match quality tracking, pipeline audit

SCAN DISPLAY:
- Use matchedCount (withGeometry) for 'cu geometrie' — ALWAYS adds up
  with noGeomCount to equal totalImmovables (ground truth arithmetic)
- Show remoteGisCount separately as 'Layer GIS: N features (se descarca toate)'
- When remoteGisCount != matchedCount, show matching detail with breakdown
  (X potrivite + cadRef/ID split) so mismatches are transparent
- Workflow preview step 1 still uses remoteGisCount (correct: all GIS
  features get downloaded regardless of matching)

MATCH QUALITY TRACKING:
- New fields: matchedByRef, matchedById in NoGeomScanResult
- Track how many immovables matched by cadastral ref vs by IMMOVABLE_ID
- Console log match quality for server-side debugging
- scannedAt timestamp for audit trail

PIPELINE AUDIT (export report):
- New 'pipeline' section in export_report.json with full trace:
  syncedGis, noGeometry (imported/cleaned/skipped), enriched, finalDb
- raport_calitate.txt now has PIPELINE section before quality analysis
  showing exactly what happened at each step
- Capture noGeomCleaned + noGeomSkipped in addition to noGeomImported
This commit is contained in:
AI Assistant
2026-03-07 21:22:29 +02:00
parent 1e6888a32a
commit 531c3b0858
3 changed files with 96 additions and 13 deletions
@@ -118,6 +118,9 @@ export type NoGeomScanResult = {
/** Total features in the remote ArcGIS TERENURI_ACTIVE layer */
remoteGisCount: number;
noGeomCount: number;
/** Match quality: how many matched by cadastral ref vs immovable ID */
matchedByRef: number;
matchedById: number;
/** Quality breakdown of no-geometry items */
qualityBreakdown: NoGeomQuality;
/** Sample of immovable identifiers without geometry */
@@ -142,6 +145,8 @@ export type NoGeomScanResult = {
localDbEnrichedComplete: number;
/** Whether local sync is fresh (< 7 days) */
localSyncFresh: boolean;
/** Timestamp of the scan (for audit trail) */
scannedAt: string;
/** Error message if workspace couldn't be resolved */
error?: string;
};
@@ -181,6 +186,8 @@ export async function scanNoGeometryParcels(
withGeometry: 0,
remoteGisCount: 0,
noGeomCount: 0,
matchedByRef: 0,
matchedById: 0,
qualityBreakdown: {
withCadRef: 0,
withPaperCad: 0,
@@ -198,6 +205,7 @@ export async function scanNoGeometryParcels(
localDbEnriched: 0,
localDbEnrichedComplete: 0,
localSyncFresh: false,
scannedAt: new Date().toISOString(),
error: `Nu s-a putut determina workspace-ul (județul) pentru SIRUTA ${siruta}`,
};
}
@@ -248,16 +256,25 @@ export async function scanNoGeometryParcels(
legalArea?: number;
}> = [];
let matchedByRef = 0;
let matchedById = 0;
for (const item of allImmovables) {
const cadRef = normalizeCadRef(item.identifierDetails ?? "");
const immPk = Number(item.immovablePk ?? 0);
const immId = normalizeId(item.immovablePk);
// Present in remote GIS layer by cadastral ref? → has geometry
if (cadRef && remoteCadRefs.has(cadRef)) continue;
if (cadRef && remoteCadRefs.has(cadRef)) {
matchedByRef++;
continue;
}
// Present in remote GIS layer by IMMOVABLE_ID? → has geometry
if (immId && remoteImmIds.has(immId)) continue;
if (immId && remoteImmIds.has(immId)) {
matchedById++;
continue;
}
noGeomItems.push({
immovablePk: immPk,
@@ -334,6 +351,12 @@ export async function scanNoGeometryParcels(
// withGeometry = immovables that MATCHED a GIS feature (always adds up)
const matchedCount = allImmovables.length - noGeomItems.length;
console.log(
`[no-geom-scan] Match quality: ${matchedCount} total (${matchedByRef} by cadRef, ${matchedById} by immId)` +
` | GIS layer: ${remoteFeatures.length} features | Immovables: ${allImmovables.length}` +
` | Unmatched GIS: ${remoteFeatures.length - matchedCount}`,
);
// Quality analysis of no-geom items
let qWithCadRef = 0;
let qWithPaperCad = 0;
@@ -370,6 +393,8 @@ export async function scanNoGeometryParcels(
withGeometry: matchedCount,
remoteGisCount: remoteFeatures.length,
noGeomCount: noGeomItems.length,
matchedByRef,
matchedById,
qualityBreakdown: {
withCadRef: qWithCadRef,
withPaperCad: qWithPaperCad,
@@ -387,6 +412,7 @@ export async function scanNoGeometryParcels(
localDbEnriched: localEnriched,
localDbEnrichedComplete: localEnrichedComplete,
localSyncFresh: syncFresh,
scannedAt: new Date().toISOString(),
};
}