viabiliza/service/fileFormat.js
gabriel.amancio 801d3fa90d REFAC: consumo do endpoint /lote, dedup de helpers e colunas por caixa
- 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>
2026-07-21 15:49:29 -03:00

73 lines
2.3 KiB
JavaScript

const fs = require('fs');
const path = require('path');
// Helpers de detecção de formato/cabeçalho compartilhados entre viabilidadeService e csvService.
// Antes estavam duplicados nos dois arquivos. Todos normalizam internamente (normalizeHeader
// é idempotente), então funcionam tanto com cabeçalhos crus quanto já normalizados.
function normalizeHeader(value) {
return String(value || '')
.trim()
.toLowerCase()
.normalize('NFD')
.replace(/[̀-ͯ]/g, '')
.replace(/[_-]+/g, ' ')
.replace(/\s+/g, ' ');
}
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 hasCepHeader(headers) {
return headers.map(normalizeHeader).some(header => /\bcep\b/.test(header) || header === 'codigo postal');
}
function hasAddressOrNumberHeader(headers) {
return headers.map(normalizeHeader).some(header => ['numero', 'num', 'nº', 'n°'].includes(header)
|| header.includes('endereco')
|| header.includes('logradouro'));
}
function hasGeoHeaders(headers) {
const norm = headers.map(normalizeHeader);
return norm.some(h => /\blat(itude)?\b/.test(h))
&& norm.some(h => /\blo?n[g]?(itude)?\b/.test(h));
}
module.exports = {
normalizeHeader,
isExcelFile,
detectDelimiter,
hasCepHeader,
hasAddressOrNumberHeader,
hasGeoHeaders
};