Guilherme Silberfarb Costa
knn e melhorias esteticas
21fc066
const API_BASE = import.meta.env.VITE_API_BASE || 'http://localhost:8000'
const AUTH_TOKEN_STORAGE_KEY = 'mesa_auth_token'
let AUTH_TOKEN = ''
if (typeof window !== 'undefined') {
AUTH_TOKEN = window.localStorage.getItem(AUTH_TOKEN_STORAGE_KEY) || ''
}
export function setAuthToken(token) {
const value = String(token || '').trim()
AUTH_TOKEN = value
if (typeof window === 'undefined') return
if (value) {
window.localStorage.setItem(AUTH_TOKEN_STORAGE_KEY, value)
} else {
window.localStorage.removeItem(AUTH_TOKEN_STORAGE_KEY)
}
}
export function getAuthToken() {
return AUTH_TOKEN
}
function authHeaders(extraHeaders = {}) {
const headers = { ...extraHeaders }
if (AUTH_TOKEN) headers['X-Auth-Token'] = AUTH_TOKEN
return headers
}
function emitAuthRequired(detail = 'Login obrigatorio') {
if (typeof window === 'undefined') return
const event = new CustomEvent('mesa:auth-required', { detail: { message: detail } })
window.dispatchEvent(event)
}
function describeFetchFailure(cause) {
const name = String(cause?.name || '').trim()
const message = String(cause?.message || '').trim()
if (name === 'AbortError') {
return 'a requisicao foi interrompida antes de concluir'
}
if (typeof navigator !== 'undefined' && navigator.onLine === false) {
return 'sem conexao com a internet'
}
if (/failed to fetch|networkerror|load failed/i.test(message)) {
return 'conexao interrompida durante a transferencia ou bloqueio de rede/CORS/proxy'
}
return message || 'falha de rede nao identificada'
}
function buildFetchError(path, stage, cause, status = null) {
const endpoint = String(path || '').trim() || '(endpoint desconhecido)'
const reason = describeFetchFailure(cause)
const statusNumber = Number(status)
const statusLabel = Number.isFinite(statusNumber) ? ` (HTTP ${statusNumber})` : ''
const action = stage === 'body'
? `Falha ao receber dados de ${endpoint}${statusLabel}`
: `Falha ao conectar com ${endpoint}`
const error = new Error(`${action}. Motivo: ${reason}.`)
error.path = endpoint
error.stage = stage
error.reason = reason
if (Number.isFinite(statusNumber)) {
error.status = statusNumber
}
error.cause = cause
return error
}
async function fetchWithDiagnostics(path, options = {}) {
try {
return await fetch(`${API_BASE}${path}`, options)
} catch (cause) {
throw buildFetchError(path, 'request', cause)
}
}
async function responseBlobWithDiagnostics(response, path = '') {
try {
return await response.blob()
} catch (cause) {
throw buildFetchError(path, 'body', cause, response?.status)
}
}
async function handleResponse(response, path = '') {
if (!response.ok) {
let detail = 'Erro inesperado'
try {
const data = await response.json()
detail = data.detail || detail
} catch {
detail = response.statusText || detail
}
const detailMessage = typeof detail === 'string'
? detail
: String(detail?.message || response.statusText || 'Erro inesperado')
if (response.status === 401 && path !== '/api/auth/login') {
setAuthToken('')
emitAuthRequired(detailMessage)
}
const error = new Error(detailMessage)
error.status = response.status
error.path = path
error.detail = detail
throw error
}
return response
}
async function postJson(path, body) {
const response = await fetchWithDiagnostics(path, {
method: 'POST',
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify(body),
})
await handleResponse(response, path)
return response.json()
}
async function postForm(path, formData) {
const response = await fetchWithDiagnostics(path, {
method: 'POST',
headers: authHeaders(),
body: formData,
})
await handleResponse(response, path)
return response.json()
}
async function getJson(path) {
const response = await fetchWithDiagnostics(path, { headers: authHeaders() })
await handleResponse(response, path)
return response.json()
}
async function getBlob(path) {
const response = await fetchWithDiagnostics(path, { headers: authHeaders() })
await handleResponse(response, path)
return responseBlobWithDiagnostics(response, path)
}
export function downloadBlob(blob, fileName) {
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = fileName
document.body.appendChild(a)
a.click()
a.remove()
URL.revokeObjectURL(url)
}
export const api = {
authLogin: (usuario, matricula) => postJson('/api/auth/login', { usuario, matricula }),
authMe: () => getJson('/api/auth/me'),
authLogout: () => postJson('/api/auth/logout', {}),
createSession: () => postJson('/api/sessions', {}),
pesquisaAdminConfig: () => getJson('/api/pesquisa/admin-config'),
pesquisaAdminConfigSalvar: (campos = {}) => postJson('/api/pesquisa/admin-config', { campos }),
pesquisarModelos(filtros = {}) {
const params = new URLSearchParams()
Object.entries(filtros).forEach(([key, value]) => {
if (value === null || value === undefined) return
const text = String(value).trim()
if (!text) return
params.append(key, text)
})
const query = params.toString()
return getJson(query ? `/api/pesquisa/modelos?${query}` : '/api/pesquisa/modelos')
},
pesquisarMapaModelos(modelosIds = []) {
return postJson('/api/pesquisa/mapa-modelos', { modelos_ids: modelosIds })
},
uploadElaboracaoFile(sessionId, file) {
const form = new FormData()
form.append('session_id', sessionId)
form.append('file', file)
return postForm('/api/elaboracao/upload', form)
},
elaboracaoRepositorioModelos: () => getJson('/api/elaboracao/repositorio-modelos'),
elaboracaoRepositorioCarregar: (sessionId, modeloId) => postJson('/api/elaboracao/repositorio-carregar', { session_id: sessionId, modelo_id: modeloId }),
confirmSheet: (sessionId, sheetName) => postJson('/api/elaboracao/confirm-sheet', { session_id: sessionId, sheet_name: sheetName }),
mapCoords: (sessionId, colLat, colLon) => postJson('/api/elaboracao/map-coords', { session_id: sessionId, col_lat: colLat, col_lon: colLon }),
geocodificar: (sessionId, colCdlog, colNum, auto200) => postJson('/api/elaboracao/geocodificar', { session_id: sessionId, col_cdlog: colCdlog, col_num: colNum, auto_200: auto200 }),
geocodificarCorrecoes: (sessionId, correcoes, auto200) => postJson('/api/elaboracao/geocodificar-correcoes', { session_id: sessionId, correcoes, auto_200: auto200 }),
geocodificarReiniciar: (sessionId) => postJson('/api/elaboracao/geocodificar-reiniciar', { session_id: sessionId }),
geocodificarExcluirCoords: (sessionId) => postJson('/api/elaboracao/geocodificar-excluir-coords', { session_id: sessionId }),
applySelection(payload) {
return postJson('/api/elaboracao/apply-selection', payload)
},
classifyElaboracaoX: (sessionId, colunasX) => postJson('/api/elaboracao/classify-x', { session_id: sessionId, colunas_x: colunasX }),
searchTransformations: (sessionId, grauCoef, grauF, transformacoesFixas = {}, transformacaoYFixa = 'Livre') => postJson('/api/elaboracao/search-transformations', {
session_id: sessionId,
grau_min_coef: grauCoef,
grau_min_f: grauF,
transformacoes_fixas: transformacoesFixas,
transformacao_y_fixa: transformacaoYFixa,
}),
adoptSuggestion: (sessionId, indice) => postJson('/api/elaboracao/adopt-suggestion', { session_id: sessionId, indice }),
fitModel(payload) {
return postJson('/api/elaboracao/fit-model', payload)
},
updateModelDispersao: (sessionId, payload) => postJson('/api/elaboracao/model-dispersao', {
session_id: sessionId,
...(payload || {}),
}),
getDispersaoInterativo: (sessionId, alvo) => postJson('/api/elaboracao/dispersao-interativo', {
session_id: sessionId,
alvo,
}),
getDiagnosticoInterativo: (sessionId, grafico) => postJson('/api/elaboracao/diagnostico-interativo', {
session_id: sessionId,
grafico,
}),
previewTransformElab: (sessionId, transformacaoY, transformacoesX) => postJson('/api/elaboracao/transform-preview', {
session_id: sessionId,
transformacao_y: transformacaoY,
transformacoes_x: transformacoesX,
}),
applyOutlierFilters: (sessionId, filtros) => postJson('/api/elaboracao/outliers/apply-filters', { session_id: sessionId, filtros }),
applyOutlierFiltersRecursive: (sessionId, filtros) => postJson('/api/elaboracao/outliers/apply-filters-recursive', { session_id: sessionId, filtros }),
restartOutlierIteration: (sessionId, outliersTexto, reincluirTexto, grauCoef, grauF) => postJson('/api/elaboracao/outliers/restart', {
session_id: sessionId,
outliers_texto: outliersTexto,
reincluir_texto: reincluirTexto,
grau_min_coef: grauCoef,
grau_min_f: grauF,
}),
outlierSummary: (sessionId, outliersTexto, reincluirTexto) => postJson('/api/elaboracao/outliers/summary', {
session_id: sessionId,
outliers_texto: outliersTexto,
reincluir_texto: reincluirTexto,
}),
clearOutlierHistory: (sessionId) => postJson('/api/elaboracao/outliers/clear-history', { session_id: sessionId }),
evaluationFieldsElab: (sessionId) => postJson('/api/elaboracao/evaluation/fields', { session_id: sessionId }),
evaluationCalculateElab: (sessionId, valoresX, indiceBase) => postJson('/api/elaboracao/evaluation/calculate', {
session_id: sessionId,
valores_x: valoresX,
indice_base: indiceBase,
}),
evaluationKnnDetailsElab: (sessionId, valoresX) => postJson('/api/elaboracao/evaluation/knn-details', {
session_id: sessionId,
valores_x: valoresX,
}),
evaluationClearElab: (sessionId) => postJson('/api/elaboracao/evaluation/clear', { session_id: sessionId }),
evaluationDeleteElab: (sessionId, indice, indiceBase) => postJson('/api/elaboracao/evaluation/delete', {
session_id: sessionId,
indice,
indice_base: indiceBase,
}),
getAvaliadores: () => getJson('/api/elaboracao/avaliadores'),
evaluationBaseElab: (sessionId, indiceBase) => postJson('/api/elaboracao/evaluation/base', {
session_id: sessionId,
indice_base: indiceBase,
}),
exportEvaluationElab: async (sessionId) => getBlob(`/api/elaboracao/evaluation/export?session_id=${encodeURIComponent(sessionId)}`),
exportEquationElab: async (sessionId, mode = 'excel') => getBlob(`/api/elaboracao/equation/export?session_id=${encodeURIComponent(sessionId)}&mode=${encodeURIComponent(String(mode || 'excel'))}`),
exportModel: async (sessionId, nomeArquivo, elaborador) => {
const path = '/api/elaboracao/export-model'
const response = await fetchWithDiagnostics(path, {
method: 'POST',
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify({ session_id: sessionId, nome_arquivo: nomeArquivo, elaborador }),
})
await handleResponse(response, path)
return responseBlobWithDiagnostics(response, path)
},
exportBase: (sessionId, filtered = true) => getBlob(`/api/elaboracao/export-base?session_id=${encodeURIComponent(sessionId)}&filtered=${String(filtered)}`),
updateElaboracaoMap: (sessionId, variavelMapa, modoMapa = 'pontos') => postJson('/api/elaboracao/map/update', {
session_id: sessionId,
variavel_mapa: variavelMapa,
modo_mapa: modoMapa,
}),
updateElaboracaoResiduosMap: (sessionId, variavelMapa, modoMapa = 'pontos', escalaExtremoAbs = null) => postJson('/api/elaboracao/residuos/map/update', {
session_id: sessionId,
variavel_mapa: variavelMapa,
modo_mapa: modoMapa,
escala_extremo_abs: escalaExtremoAbs,
}),
previewMarketDateColumn: (sessionId, colunaData) => postJson('/api/elaboracao/market-date/preview', { session_id: sessionId, coluna_data: colunaData }),
applyMarketDateColumn: (sessionId, colunaData) => postJson('/api/elaboracao/market-date/apply', { session_id: sessionId, coluna_data: colunaData }),
getContext: (sessionId) => getJson(`/api/elaboracao/context?session_id=${encodeURIComponent(sessionId)}`),
uploadVisualizacaoFile(sessionId, file) {
const form = new FormData()
form.append('session_id', sessionId)
form.append('file', file)
return postForm('/api/visualizacao/upload', form)
},
visualizacaoRepositorioModelos: () => getJson('/api/visualizacao/repositorio-modelos'),
visualizacaoRepositorioCarregar: (sessionId, modeloId) => postJson('/api/visualizacao/repositorio-carregar', { session_id: sessionId, modelo_id: modeloId }),
exibirVisualizacao: (sessionId) => postJson('/api/visualizacao/exibir', { session_id: sessionId }),
evaluationContextViz: (sessionId) => postJson('/api/visualizacao/evaluation/context', { session_id: sessionId }),
updateVisualizacaoMap: (sessionId, variavelMapa) => postJson('/api/visualizacao/map/update', { session_id: sessionId, variavel_mapa: variavelMapa }),
evaluationFieldsViz: (sessionId) => postJson('/api/visualizacao/evaluation/fields', { session_id: sessionId }),
evaluationCalculateViz: (sessionId, valoresX, indiceBase) => postJson('/api/visualizacao/evaluation/calculate', {
session_id: sessionId,
valores_x: valoresX,
indice_base: indiceBase,
}),
evaluationKnnDetailsViz: (sessionId, valoresX) => postJson('/api/visualizacao/evaluation/knn-details', {
session_id: sessionId,
valores_x: valoresX,
}),
evaluationClearViz: (sessionId) => postJson('/api/visualizacao/evaluation/clear', { session_id: sessionId }),
evaluationDeleteViz: (sessionId, indice, indiceBase) => postJson('/api/visualizacao/evaluation/delete', {
session_id: sessionId,
indice,
indice_base: indiceBase,
}),
evaluationBaseViz: (sessionId, indiceBase) => postJson('/api/visualizacao/evaluation/base', {
session_id: sessionId,
indice_base: indiceBase,
}),
exportEvaluationViz: (sessionId) => getBlob(`/api/visualizacao/evaluation/export?session_id=${encodeURIComponent(sessionId)}`),
exportEquationViz: (sessionId, mode = 'excel') => getBlob(`/api/visualizacao/equation/export?session_id=${encodeURIComponent(sessionId)}&mode=${encodeURIComponent(String(mode || 'excel'))}`),
clearVisualizacao: (sessionId) => postJson('/api/visualizacao/clear', { session_id: sessionId }),
repositorioListar: () => getJson('/api/repositorio/modelos'),
repositorioUpload(files = [], { confirmarSubstituicao = false } = {}) {
const form = new FormData()
files.forEach((file) => {
form.append('files', file)
})
form.append('confirmar_substituicao', confirmarSubstituicao ? 'true' : 'false')
return postForm('/api/repositorio/upload', form)
},
repositorioDelete: (modelosIds = []) => postJson('/api/repositorio/delete', { modelos_ids: modelosIds }),
logsStatus: () => getJson('/api/logs/status'),
logsEvents({ scope = '', usuario = '', limit = 200 } = {}) {
const params = new URLSearchParams()
const scopeText = String(scope || '').trim()
const usuarioText = String(usuario || '').trim()
const limitNumber = Number(limit)
if (scopeText) params.set('scope', scopeText)
if (usuarioText) params.set('usuario', usuarioText)
if (Number.isFinite(limitNumber) && limitNumber > 0) params.set('limit', String(Math.floor(limitNumber)))
const query = params.toString()
return getJson(query ? `/api/logs/events?${query}` : '/api/logs/events')
},
}