FEAT: Adiciona validação caso endereço esteja acima do raio de 5000 no geogrid.
This commit is contained in:
parent
81ab094433
commit
bb1b9912d9
2
.env
2
.env
@ -1,5 +1,5 @@
|
||||
GOOGLE_API_KEY="AIzaSyCTrRFGKCZSspHRmTWQiclmIEOg-LROgyo"
|
||||
API_URL="https://plutao.geogridmaps.com.br/vale/api/v3/viabilidade/raio"
|
||||
API_KEY="6d717e972ba17c7cf0ab731801b8bbeac2f281e5"
|
||||
COKIE="PHPSESSID=6d717e972ba17c7cf0ab731801b8bbeac2f281e5"
|
||||
COOKIE="PHPSESSID=6d717e972ba17c7cf0ab731801b8bbeac2f281e5"
|
||||
PORT="3000"
|
||||
14
app.js
14
app.js
@ -48,8 +48,8 @@ function createApp() {
|
||||
.json({ error: "geocode não encontrado (Google)" });
|
||||
const lat = Number(geo.lat);
|
||||
const lon = Number(geo.lon);
|
||||
const result = await getMinDistance(lat, lon);
|
||||
if (result && result.dist !== undefined) {
|
||||
const result = await getMinDistance(lat, lon);
|
||||
if (result && result.dist !== undefined) {
|
||||
// preciso criar 2 campos: Link Dedicado e Link Não Dedicado em que o dedicado é viável até 1000m e o não dedicado até 500m
|
||||
if (result.dist <= 500) {
|
||||
return res.json({
|
||||
@ -80,6 +80,16 @@ function createApp() {
|
||||
});
|
||||
}
|
||||
}
|
||||
// quando a consulta não retorna um resultado válido, responder como 'Não viável'
|
||||
// isso evita que a UI fique presa em "Consultando..." esperando uma resposta
|
||||
return res.json({
|
||||
endereco,
|
||||
latitude: lat,
|
||||
longitude: lon,
|
||||
distancia: null,
|
||||
dedicado: "Não viável",
|
||||
naoDedicado: "Não viável",
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return res.status(500).json({ error: "erro na consulta" });
|
||||
|
||||
@ -23,126 +23,138 @@ const { normalizePartnerSigla } = require("./partnerSiglaService");
|
||||
const MAX_RETRIES = 5;
|
||||
|
||||
async function getMinDistance(lat, lon) {
|
||||
// tenta várias vezes com backoff exponencial; trata 429 usando Retry-After se disponível
|
||||
let attempt = 0;
|
||||
while (attempt < MAX_RETRIES) {
|
||||
try {
|
||||
// envia também o raio (em metros) - API espera esse parâmetro em várias rotas
|
||||
const resp = await axios.get(API_URL, {
|
||||
headers: HEADERS,
|
||||
params: {
|
||||
raio: 5000,
|
||||
latitude: lat,
|
||||
longitude: lon,
|
||||
"itens[]": ["caixa"],
|
||||
consultarPasta: "S",
|
||||
},
|
||||
timeout: 10000,
|
||||
});
|
||||
const data = resp.data;
|
||||
const registros = data && data.registros ? data.registros : [];
|
||||
// find registros that have a numeric distancia and keep original object for robust extraction
|
||||
const candidates = registros
|
||||
.map((r) => ({ raw: r, distanciaRaw: r && r.distancia }))
|
||||
.map((o) => ({
|
||||
raw: o.raw,
|
||||
num:
|
||||
o.distanciaRaw !== undefined &&
|
||||
o.distanciaRaw !== null &&
|
||||
o.distanciaRaw !== ""
|
||||
? Number(o.distanciaRaw)
|
||||
: null,
|
||||
}))
|
||||
.filter((x) => x.num !== null && !Number.isNaN(x.num));
|
||||
if (candidates.length) {
|
||||
candidates.sort((a, b) => a.num - b.num);
|
||||
const best = candidates[0];
|
||||
const r = best.raw || {};
|
||||
// robust extraction of pasta sigla with fallbacks
|
||||
let pastaSigla = null;
|
||||
try {
|
||||
if (r.pasta) {
|
||||
if (typeof r.pasta === "string" && r.pasta.trim())
|
||||
pastaSigla = r.pasta.trim();
|
||||
else if (r.pasta.sigla && String(r.pasta.sigla).trim())
|
||||
pastaSigla = String(r.pasta.sigla).trim();
|
||||
else if (
|
||||
r.pasta.cidade &&
|
||||
r.pasta.cidade.sigla &&
|
||||
String(r.pasta.cidade.sigla).trim()
|
||||
)
|
||||
pastaSigla = String(r.pasta.cidade.sigla).trim();
|
||||
}
|
||||
} catch (e) {
|
||||
pastaSigla = null;
|
||||
}
|
||||
// if closest has no pastaSigla, try find any candidate with non-empty sigla
|
||||
if (!pastaSigla) {
|
||||
for (let j = 0; j < candidates.length; j++) {
|
||||
const rr = candidates[j].raw || {};
|
||||
const radii = [5000, 10000, 20000];
|
||||
for (const raio of radii) {
|
||||
let attempt = 0;
|
||||
while (attempt < MAX_RETRIES) {
|
||||
try {
|
||||
// observe que aqui enviamos itens[] como string simples (algumas APIs esperam isto)
|
||||
const resp = await axios.get(API_URL, {
|
||||
headers: HEADERS,
|
||||
params: {
|
||||
raio,
|
||||
latitude: lat,
|
||||
longitude: lon,
|
||||
"itens[]": "caixa",
|
||||
consultarPasta: "S",
|
||||
},
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
const data = resp.data || {};
|
||||
const registros = data.registros || [];
|
||||
console.info(
|
||||
`[INFO] API resp. raio=${raio} lat=${lat} lon=${lon} registros=${registros.length}`
|
||||
);
|
||||
if (registros.length > 0) {
|
||||
// same extraction logic as before
|
||||
const candidates = registros
|
||||
.map((r) => ({ raw: r, distanciaRaw: r && r.distancia }))
|
||||
.map((o) => ({
|
||||
raw: o.raw,
|
||||
num:
|
||||
o.distanciaRaw !== undefined &&
|
||||
o.distanciaRaw !== null &&
|
||||
o.distanciaRaw !== ""
|
||||
? Number(o.distanciaRaw)
|
||||
: null,
|
||||
}))
|
||||
.filter((x) => x.num !== null && !Number.isNaN(x.num));
|
||||
if (candidates.length) {
|
||||
candidates.sort((a, b) => a.num - b.num);
|
||||
const best = candidates[0];
|
||||
const r = best.raw || {};
|
||||
let pastaSigla = null;
|
||||
try {
|
||||
if (rr.pasta) {
|
||||
if (typeof rr.pasta === "string" && rr.pasta.trim()) {
|
||||
pastaSigla = rr.pasta.trim();
|
||||
break;
|
||||
}
|
||||
if (rr.pasta.sigla && String(rr.pasta.sigla).trim()) {
|
||||
pastaSigla = String(rr.pasta.sigla).trim();
|
||||
break;
|
||||
}
|
||||
if (
|
||||
rr.pasta.cidade &&
|
||||
rr.pasta.cidade.sigla &&
|
||||
String(rr.pasta.cidade.sigla).trim()
|
||||
) {
|
||||
pastaSigla = String(rr.pasta.cidade.sigla).trim();
|
||||
break;
|
||||
}
|
||||
if (r.pasta) {
|
||||
if (typeof r.pasta === "string" && r.pasta.trim())
|
||||
pastaSigla = r.pasta.trim();
|
||||
else if (r.pasta.sigla && String(r.pasta.sigla).trim())
|
||||
pastaSigla = String(r.pasta.sigla).trim();
|
||||
else if (
|
||||
r.pasta.cidade &&
|
||||
r.pasta.cidade.sigla &&
|
||||
String(r.pasta.cidade.sigla).trim()
|
||||
)
|
||||
pastaSigla = String(r.pasta.cidade.sigla).trim();
|
||||
}
|
||||
} catch (e) {
|
||||
// continue
|
||||
pastaSigla = null;
|
||||
}
|
||||
if (!pastaSigla) {
|
||||
for (let j = 0; j < candidates.length; j++) {
|
||||
const rr = candidates[j].raw || {};
|
||||
try {
|
||||
if (rr.pasta) {
|
||||
if (typeof rr.pasta === "string" && rr.pasta.trim()) {
|
||||
pastaSigla = rr.pasta.trim();
|
||||
break;
|
||||
}
|
||||
if (rr.pasta.sigla && String(rr.pasta.sigla).trim()) {
|
||||
pastaSigla = String(rr.pasta.sigla).trim();
|
||||
break;
|
||||
}
|
||||
if (
|
||||
rr.pasta.cidade &&
|
||||
rr.pasta.cidade.sigla &&
|
||||
String(rr.pasta.cidade.sigla).trim()
|
||||
) {
|
||||
pastaSigla = String(rr.pasta.cidade.sigla).trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!pastaSigla)
|
||||
console.warn(
|
||||
`[WARN] Nenhuma pasta.sigla encontrada para coordenadas ${lat},${lon} (closest dist ${best.num})`
|
||||
);
|
||||
pastaSigla = normalizePartnerSigla(pastaSigla);
|
||||
return { dist: best.num, pastaSigla };
|
||||
}
|
||||
}
|
||||
if (!pastaSigla)
|
||||
console.warn(
|
||||
`[WARN] Nenhuma pasta.sigla encontrada para coordenadas ${lat},${lon} (closest dist ${best.num})`
|
||||
} else {
|
||||
// log mais detalhado para diagnóstico quando vazio
|
||||
console.debug(
|
||||
`[DEBUG] resposta vazia para raio=${raio} lat=${lat} lon=${lon} -> primeiroFragmento: ${JSON.stringify(
|
||||
Array.isArray(data.registros) ? data.registros.slice(0, 3) : data
|
||||
)}`
|
||||
);
|
||||
pastaSigla = normalizePartnerSigla(pastaSigla);
|
||||
return { dist: best.num, pastaSigla };
|
||||
}
|
||||
// sem distancias válidas
|
||||
return null;
|
||||
} catch (err) {
|
||||
attempt += 1;
|
||||
// se for 429, tente respeitar Retry-After quando disponível
|
||||
if (err.response && err.response.status === 429) {
|
||||
const ra =
|
||||
err.response.headers &&
|
||||
(err.response.headers["retry-after"] ||
|
||||
err.response.headers["Retry-After"]);
|
||||
let waitMs = BASE_BACKOFF_MS * Math.pow(2, attempt - 1);
|
||||
if (ra) {
|
||||
const raSec = parseInt(ra, 10);
|
||||
if (!isNaN(raSec)) waitMs = raSec * 1000;
|
||||
}
|
||||
// sem distancias válidas para este raio -> tenta próximo raio
|
||||
break;
|
||||
} catch (err) {
|
||||
attempt += 1;
|
||||
if (err.response && err.response.status === 429) {
|
||||
const ra =
|
||||
err.response.headers &&
|
||||
(err.response.headers["retry-after"] ||
|
||||
err.response.headers["Retry-After"]);
|
||||
let waitMs = BASE_BACKOFF_MS * Math.pow(2, attempt - 1);
|
||||
if (ra) {
|
||||
const raSec = parseInt(ra, 10);
|
||||
if (!isNaN(raSec)) waitMs = raSec * 1000;
|
||||
}
|
||||
console.warn(
|
||||
`[WARN] 429 recebido para ${lat},${lon} - aguardando ${waitMs}ms e tentando novamente (attempt ${attempt}/${MAX_RETRIES})`
|
||||
);
|
||||
await sleep(waitMs);
|
||||
continue;
|
||||
}
|
||||
const waitMs = BASE_BACKOFF_MS * Math.pow(2, attempt - 1);
|
||||
console.warn(
|
||||
`[WARN] 429 recebido para ${lat},${lon} - aguardando ${waitMs}ms e tentando novamente (attempt ${attempt}/${MAX_RETRIES})`
|
||||
`[WARN] Erro ao consultar API para ${lat},${lon}: ${err.message} - backoff ${waitMs}ms (attempt ${attempt}/${MAX_RETRIES})`
|
||||
);
|
||||
await sleep(waitMs);
|
||||
continue;
|
||||
}
|
||||
|
||||
// para outros erros de rede/timeout, aguarda backoff exponencial e tenta de novo
|
||||
const waitMs = BASE_BACKOFF_MS * Math.pow(2, attempt - 1);
|
||||
console.warn(
|
||||
`[WARN] Erro ao consultar API para ${lat},${lon}: ${err.message} - backoff ${waitMs}ms (attempt ${attempt}/${MAX_RETRIES})`
|
||||
);
|
||||
await sleep(waitMs);
|
||||
}
|
||||
// pequena espera entre mudanças de raio
|
||||
await sleep(50);
|
||||
}
|
||||
// exauriu tentativas
|
||||
console.error(`[ERROR] Exauriu retries para ${lat},${lon}`);
|
||||
|
||||
console.error(`[ERROR] Sem registros válidos para ${lat},${lon} após todos os raios`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user