viabiliza/service/viabilidadeService.js

163 lines
4.8 KiB
JavaScript

const axios = require('axios');
const fs = require('fs');
const readline = require('readline');
const path = require('path');
const XLSX = require('xlsx');
const { apiConfig, apiViabilidadeUrl, apiUrlBase } = require('../config/apiConfig');
function normalizeHeader(value) {
return String(value || '')
.trim()
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/[_-]+/g, ' ')
.replace(/\s+/g, ' ');
}
function hasHeader(headers, aliases) {
const normalizedAliases = aliases.map(normalizeHeader);
return headers.some(header => normalizedAliases.includes(header));
}
function isExcelFile(filePath) {
const ext = path.extname(filePath).toLowerCase();
if (['.xls', '.xlsx'].includes(ext)) return true;
const fileStart = fs.readFileSync(filePath).subarray(0, 512);
const signature = fileStart.subarray(0, 8);
const isZipBasedXlsx = signature[0] === 0x50 && signature[1] === 0x4b;
const isOleBasedXls = signature[0] === 0xd0
&& signature[1] === 0xcf
&& signature[2] === 0x11
&& signature[3] === 0xe0
&& signature[4] === 0xa1
&& signature[5] === 0xb1
&& signature[6] === 0x1a
&& signature[7] === 0xe1;
const startText = fileStart.toString('latin1').trimStart().toLowerCase();
const isHtmlExcel = startText.startsWith('<html')
|| startText.startsWith('<!doctype html')
|| startText.includes('<table');
return isZipBasedXlsx || isOleBasedXls || isHtmlExcel;
}
function detectDelimiter(line) {
const delimiters = [';', '\t', ','];
return delimiters
.map(delimiter => ({ delimiter, count: line.split(delimiter).length }))
.sort((a, b) => b.count - a.count)[0].delimiter;
}
function findHeaderRow(rows) {
return rows.find(row => {
const headers = row.map(normalizeHeader);
const hasCepNumero = hasCepHeader(headers) && hasAddressOrNumberHeader(headers);
const hasGeo = hasGeoHeaders(headers);
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(/^\uFEFF/, '').replace(/\r$/, '');
if (!line.trim()) continue;
const headers = line.split(detectDelimiter(line)).map(normalizeHeader);
const hasCepNumero = hasCepHeader(headers) && hasAddressOrNumberHeader(headers);
const hasGeo = hasGeoHeaders(headers);
if (hasCepNumero || hasGeo) {
rl.close();
return headers;
}
}
rl.close();
return [];
}
function hasCepHeader(headers) {
return headers.some(header => /\bcep\b/.test(header) || header === 'codigo postal');
}
function hasAddressOrNumberHeader(headers) {
return headers.some(header => ['numero', 'num', 'nº', 'n°'].includes(header)
|| header.includes('endereco')
|| header.includes('logradouro'));
}
function hasGeoHeaders(headers) {
return hasHeader(headers, ['latitude', 'lat'])
&& hasHeader(headers, ['longitude', 'long', 'lng', 'lon']);
}
async function consultarViabilidade(data) {
try {
const dataType = await discoverDataType(data);
let endpoint = apiUrlBase;
if (dataType === 'geolocalizacao') {
endpoint += 'viabilidade/lat-long';
} else {
endpoint += 'viabilidade';
}
const response = await axios.post(endpoint, data, {
timeout: 10000,
headers: { 'Content-Type': 'application/json' }
});
console.log('Resposta da API de viabilidade:', response.data);
return (response.data);
} catch (error) {
throw error;
}
}
// Preciso de uma função para verificar se os dados vindos são de CEP ou de geolocalização
async function discoverDataType(input) {
if (typeof input === 'string') {
const headers = isExcelFile(input)
? readExcelHeaders(input)
: await readDelimitedHeaders(input);
const hasCepNumero = hasCepHeader(headers) && hasAddressOrNumberHeader(headers);
if (hasCepNumero) {
return 'cep';
} else if (hasGeoHeaders(headers)) {
return 'geolocalizacao';
} else {
return 'unknown';
}
} else if (typeof input === 'object') {
// Trata como objeto de dados
if (input.cep && input.numero) {
return 'cep';
} else if (input.latitude !== undefined && input.longitude !== undefined) {
return 'geolocalizacao';
} else {
return 'unknown';
}
} else {
return 'unknown';
}
}
module.exports = { consultarViabilidade, discoverDataType };