viabiliza/service/csvService.js
gabriel.pereira b3bca576da REFACTOR: Remoção de serviços obsoletos e implementação da nova funcionalidade de viabilidade pela API de contratação
- Removidos os arquivos não utilizados: fetchService, geocodeService, normalizeService e retryService.

- Adicionado o viabilidadeController para gerenciar consultas de viabilidade e upload de arquivos CSV.

- Criadas as viabilidadeRoutes para tratar as rotas da API relacionadas à viabilidade.

- Implementado o csvService para processamento de arquivos CSV e integração com as consultas de viabilidade.

- Criado o jobStore.service para gerenciamento do estado dos jobs durante o processamento do CSV.

- Desenvolvido o viabilidadeService para integração com a API de viabilidade.
2025-12-30 09:16:07 -03:00

145 lines
4.4 KiB
JavaScript

const { consultarViabilidade } = 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)
async function countValidLines(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 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');
isHeader = false;
continue;
}
const cols = line.split(';').map(c => c.trim());
const cep = idxCep >= 0 ? String(cols[idxCep] || '').replace(/\D/g, '') : '';
const numero = idxNumero >= 0 ? cols[idxNumero] : '';
if (cep && numero) total++;
}
return total;
}
// nova função: processa CSV linha a linha, chama consultarViabilidade e gera CSV de saída
async function processCsvFile(inputPath) {
const total = await countValidLines(inputPath);
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;
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');
// 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 !== '');
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
}
try {
const viab = await consultarViabilidade({ cep, numero });
const distancia = viab.distancia ?? (viab.raw && (viab.raw.distancia || viab.raw.distance)) ?? '';
const endereco = `${(viab.logradouro) || ''}, ${(viab.bairro) || ''}, ${(viab.cidade) || ''}/${(viab.estado) || ''}, ${(viab.cep) || ''}`.trim().replace(/^[, ]+|[, ]+$/g, '');
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, `/download/${path.basename(outputPath)}`);
return { jobId, outputPath };
}
module.exports = { processCsvFile };