- csvService/viabilidadeService passam a consumir o endpoint /viabilidade/lote da API em blocos, no lugar de 1 requisição HTTP por linha. - Helpers de detecção de formato/cabeçalho extraídos para fileFormat.js (remove duplicação entre os dois serviços). - Planilha de saída: 4 colunas por caixa (Provedor, Não Dedicado, Dedicado, Distância Trajeto) para as 2 caixas mais próximas; Caixa 2 replica a Caixa 1 quando não há segunda caixa / sem cobertura. - parseCoordinate passa a aceitar direção O (Oeste => negativo) e L (Leste). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
107 lines
3.4 KiB
JavaScript
107 lines
3.4 KiB
JavaScript
const axios = require('axios');
|
||
const fs = require('fs');
|
||
const readline = require('readline');
|
||
const XLSX = require('xlsx');
|
||
const { apiUrlBase } = require('../config/apiConfig');
|
||
const {
|
||
normalizeHeader,
|
||
isExcelFile,
|
||
detectDelimiter,
|
||
hasCepHeader,
|
||
hasAddressOrNumberHeader,
|
||
hasGeoHeaders
|
||
} = require('./fileFormat');
|
||
|
||
function findHeaderRow(rows) {
|
||
return rows.find(row => {
|
||
const hasCepNumero = hasCepHeader(row) && hasAddressOrNumberHeader(row);
|
||
const hasGeo = hasGeoHeaders(row);
|
||
return hasCepNumero || hasGeo;
|
||
}) || [];
|
||
}
|
||
|
||
function readExcelHeaders(filePath) {
|
||
const workbook = XLSX.readFile(filePath, { cellDates: false, raw: false });
|
||
const firstSheetName = workbook.SheetNames[0];
|
||
if (!firstSheetName) return [];
|
||
|
||
const rows = XLSX.utils.sheet_to_json(workbook.Sheets[firstSheetName], {
|
||
header: 1,
|
||
blankrows: true,
|
||
defval: ''
|
||
});
|
||
|
||
return findHeaderRow(rows).map(normalizeHeader);
|
||
}
|
||
|
||
async function readDelimitedHeaders(filePath) {
|
||
const instream = fs.createReadStream(filePath, { encoding: 'utf8' });
|
||
const rl = readline.createInterface({ input: instream, crlfDelay: Infinity });
|
||
|
||
for await (const rawLine of rl) {
|
||
const line = rawLine.replace(/^/, '').replace(/\r$/, '');
|
||
if (!line.trim()) continue;
|
||
const headers = line.split(detectDelimiter(line)).map(normalizeHeader);
|
||
if ((hasCepHeader(headers) && hasAddressOrNumberHeader(headers)) || hasGeoHeaders(headers)) {
|
||
rl.close();
|
||
return headers;
|
||
}
|
||
}
|
||
|
||
rl.close();
|
||
return [];
|
||
}
|
||
|
||
// Verifica se os dados vindos são de CEP ou de geolocalização (aceita caminho de arquivo ou objeto).
|
||
async function discoverDataType(input) {
|
||
if (typeof input === 'string') {
|
||
const headers = isExcelFile(input)
|
||
? readExcelHeaders(input)
|
||
: await readDelimitedHeaders(input);
|
||
|
||
if (hasCepHeader(headers) && hasAddressOrNumberHeader(headers)) return 'cep';
|
||
if (hasGeoHeaders(headers)) return 'geolocalizacao';
|
||
return 'unknown';
|
||
}
|
||
|
||
if (input && typeof input === 'object') {
|
||
if (input.cep && input.numero) return 'cep';
|
||
if (input.latitude !== undefined && input.longitude !== undefined) return 'geolocalizacao';
|
||
return 'unknown';
|
||
}
|
||
|
||
return 'unknown';
|
||
}
|
||
|
||
// Consulta individual (manual / uma linha). Escolhe o endpoint conforme o tipo dos dados.
|
||
async function consultarViabilidade(data) {
|
||
const dataType = await discoverDataType(data);
|
||
const endpoint = apiUrlBase + (dataType === 'geolocalizacao' ? 'viabilidade/lat-long' : 'viabilidade');
|
||
|
||
const response = await axios.post(endpoint, data, {
|
||
// A API agora faz geocode + GeoGrid + trajeto (várias chamadas externas);
|
||
// o cep-promise também pode ser lento. 30s evita timeout prematuro.
|
||
timeout: 30000,
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
return response.data;
|
||
}
|
||
|
||
// Consulta em LOTE: envia um array de itens ({cep,numero} ou {latitude,longitude}) de uma só vez.
|
||
// Retorna o array `resultados` da API, alinhado por índice ao array enviado.
|
||
async function consultarViabilidadeLote(itens, modo) {
|
||
const endpoint = apiUrlBase + 'viabilidade/lote';
|
||
const body = { itens };
|
||
if (modo) body.modo = modo;
|
||
|
||
const response = await axios.post(endpoint, body, {
|
||
timeout: 180000,
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
|
||
const data = response.data || {};
|
||
return Array.isArray(data.resultados) ? data.resultados : [];
|
||
}
|
||
|
||
module.exports = { consultarViabilidade, consultarViabilidadeLote, discoverDataType };
|