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("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> = {
|
||||
siruta: validated.siruta,
|
||||
generatedAt: new Date().toISOString(),
|
||||
@@ -547,11 +625,7 @@ export async function POST(req: Request) {
|
||||
terenuri: {
|
||||
count: terenuriGeoFeatures.length,
|
||||
totalInDb: dbTerenuri.length,
|
||||
noGeometryCount: dbTerenuri.filter(
|
||||
(r) =>
|
||||
(r as unknown as { geometrySource: string | null })
|
||||
.geometrySource === "NO_GEOMETRY",
|
||||
).length,
|
||||
noGeometryCount: noGeomRecords.length,
|
||||
},
|
||||
cladiri: { count: cladiriGeoFeatures.length },
|
||||
syncSkipped: {
|
||||
@@ -560,6 +634,11 @@ export async function POST(req: Request) {
|
||||
},
|
||||
includeNoGeometry: hasNoGeom,
|
||||
noGeomImported,
|
||||
qualityAnalysis: {
|
||||
all: qualityAll,
|
||||
withGeometry: qualityGeom,
|
||||
noGeometry: qualityNoGeom,
|
||||
},
|
||||
};
|
||||
|
||||
if (validated.mode === "magic" && magicGpkg && csvContent) {
|
||||
@@ -570,6 +649,89 @@ export async function POST(req: Request) {
|
||||
hasBuildingCount,
|
||||
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));
|
||||
|
||||
@@ -580,11 +742,7 @@ export async function POST(req: Request) {
|
||||
finishPhase();
|
||||
|
||||
/* Done */
|
||||
const noGeomInDb = dbTerenuri.filter(
|
||||
(r) =>
|
||||
(r as unknown as { geometrySource: string | null }).geometrySource ===
|
||||
"NO_GEOMETRY",
|
||||
).length;
|
||||
const noGeomInDb = noGeomRecords.length;
|
||||
message = `Finalizat 100% · Terenuri ${terenuriGeoFeatures.length} · Clădiri ${cladiriGeoFeatures.length}`;
|
||||
if (noGeomInDb > 0) {
|
||||
message += ` · Fără geometrie ${noGeomInDb}`;
|
||||
@@ -592,9 +750,21 @@ export async function POST(req: Request) {
|
||||
if (!terenuriNeedsSync && !cladiriNeedsSync) {
|
||||
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";
|
||||
phase = "Finalizat";
|
||||
note = undefined;
|
||||
// note already set with quality summary above (or undefined for base mode)
|
||||
pushProgress();
|
||||
scheduleClear(jobId);
|
||||
|
||||
|
||||
@@ -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