viabiliza/service/csvService.js

176 lines
5.6 KiB
JavaScript

const { consultarViabilidade, discoverDataType } = require('./viabilidadeService');
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const { once } = require('events');
const {
createJob,
incrementProcessed,
incrementErrors,
finishJob,
failJob
} = require('./jobStore.service');
// conta linhas válidas no CSV (com CEP e Número ou Latitude e Longitude)
async function countValidLines(inputPath) {
const dataType = await discoverDataType(inputPath);
const instream = fs.createReadStream(inputPath, { encoding: 'utf8' });
const rl = readline.createInterface({ input: instream, crlfDelay: Infinity });
let isHeader = true;
let headers = [];
let idxCep = -1;
let idxNumero = -1;
let idxLatitude = -1;
let idxLongitude = -1;
let total = 0;
for await (const rawLine of rl) {
const line = rawLine.replace(/\r$/, '');
if (!line.trim()) continue;
if (isHeader) {
headers = line.split(';').map(h => h.trim());
const lower = headers.map(h => h.toLowerCase());
idxCep = lower.indexOf('cep');
idxNumero = lower.indexOf('numero');
idxLatitude = lower.indexOf('latitude');
idxLongitude = lower.indexOf('longitude');
isHeader = false;
continue;
}
const cols = line.split(';').map(c => c.trim());
if (dataType === 'cep') {
const cep = idxCep >= 0 ? String(cols[idxCep] || '').replace(/\D/g, '') : '';
const numero = idxNumero >= 0 ? cols[idxNumero] : '';
if (cep && numero) total++;
} else if (dataType === 'geolocalizacao') {
const latitude = idxLatitude >= 0 ? parseFloat(cols[idxLatitude]) : NaN;
const longitude = idxLongitude >= 0 ? parseFloat(cols[idxLongitude]) : NaN;
if (!isNaN(latitude) && !isNaN(longitude)) total++;
}
}
return total;
}
// nova função: processa CSV linha a linha, chama consultarViabilidade e gera CSV de saída
// Recebe jobId já criado no controller
async function processCsvFile(jobId, inputPath, originalName) {
const dataType = await discoverDataType(inputPath);
const total = await countValidLines(inputPath);
// Job já criado no controller
// const jobId = createJob(total);
const baseName = path.parse(inputPath).name;
const outputFilename = `processed_${Date.now()}_${baseName}.csv`;
const outputPath = path.join(__dirname, '..', 'outputs', outputFilename);
const instream = fs.createReadStream(inputPath, { encoding: 'utf8' });
const rl = readline.createInterface({ input: instream, crlfDelay: Infinity });
const outStream = fs.createWriteStream(outputPath, { encoding: 'utf8' });
outStream.write('\uFEFF');
let isHeader = true;
let headers = [];
let idxCep = -1;
let idxNumero = -1;
let idxLatitude = -1;
let idxLongitude = -1;
for await (const rawLine of rl) {
const line = rawLine.replace(/\r$/, ''); // normalize CRLF
if (!line.trim()) continue;
if (isHeader) {
headers = line
.split(';')
.map(h => h.trim())
.filter(h => h !== '');
const lower = headers.map(h => h.toLowerCase());
idxCep = lower.indexOf('cep');
idxNumero = lower.indexOf('numero');
idxLatitude = lower.indexOf('latitude');
idxLongitude = lower.indexOf('longitude');
// se não encontrar, tenta variações comuns
const idx = lower.indexOf('codigo postal');
if (idx !== -1) idxCep = idx;
const outHeaders = [...headers, 'Distancia', 'Endereco', 'Não Dedicado', 'Dedicado', 'Erro'];
outStream.write(outHeaders.join(';') + '\n');
isHeader = false;
continue;
}
const cols = line
.split(';')
.map(c => c.trim())
.filter(c => c !== '');
let dataToSend = {};
if (dataType === 'cep') {
const cepRaw = (idxCep >= 0 && cols[idxCep]) ? cols[idxCep] : '';
const cep = String(cepRaw).replace(/\D/g, ''); // keep digits only
const numero = (idxNumero >= 0 && cols[idxNumero]) ? cols[idxNumero] : '';
if (!cep || !numero) {
continue; // pula linha inválida
}
dataToSend = { cep, numero };
} else if (dataType === 'geolocalizacao') {
const latitude = (idxLatitude >= 0 && cols[idxLatitude]) ? parseFloat(cols[idxLatitude]) : NaN;
const longitude = (idxLongitude >= 0 && cols[idxLongitude]) ? parseFloat(cols[idxLongitude]) : NaN;
if (isNaN(latitude) || isNaN(longitude)) {
continue; // pula linha inválida
}
dataToSend = { latitude, longitude };
} else {
continue; // tipo desconhecido, pula
}
try {
const viab = await consultarViabilidade(dataToSend);
const distancia = viab.distancia ?? (viab.raw && (viab.raw.distancia || viab.raw.distance)) ?? '';
const endereco = viab.endereco;
if (viab.naoDedicado) {
var naoDedicado = "Viavel";
} else {
var naoDedicado = "Não Viavel";
}
if (viab.dedicado) {
var dedicado = "Viavel";
} else {
var dedicado = "Não Viavel";
}
const error = viab.error ? String(viab.error).replace(/[\r\n;]/g, ' ') : '';
const outCols = [...cols, distancia, endereco, naoDedicado, dedicado, error];
outStream.write(outCols.join(';') + '\n');
incrementProcessed(jobId);
} catch (err) {
const errMsg = (err && (err.message || String(err))).replace(/[\r\n;]/g, ' ');
const outCols = [...cols, '', '', '', '', '', '', errMsg];
outStream.write(outCols.join(';') + '\n');
incrementErrors(jobId);
incrementProcessed(jobId);
}
}
outStream.end();
await once(outStream, 'finish');
finishJob(jobId, path.basename(outputPath));
return outputPath;
}
module.exports = { processCsvFile, countValidLines };