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)) ?? ''; if (dataType === 'cep' && viab.cep) { var endereco = `${viab.logradouro || ''}, ${viab.bairro || ''}, ${viab.cidade || ''}/${viab.estado || ''}, ${viab.cep || ''}`; } else { var 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 };