Spaces:
Running
Running
| function normalizeApiBase(value) { | |
| if (value === undefined || value === null) return null | |
| const text = String(value).trim() | |
| if (!text || text === '.' || text === './') return '' | |
| return text.endsWith('/') ? text.slice(0, -1) : text | |
| } | |
| const API_BASE = normalizeApiBase(import.meta.env.VITE_API_BASE) | |
| ?? (import.meta.env.DEV ? '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 | |
| } | |
| export function apiUrl(path) { | |
| return `${API_BASE}${String(path || '')}` | |
| } | |
| 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 | |
| : Array.isArray(detail) | |
| ? detail | |
| .map((item) => { | |
| const loc = Array.isArray(item?.loc) | |
| ? item.loc.filter((part) => part !== 'body').join(' > ') | |
| : '' | |
| const msg = String(item?.msg || '').trim() | |
| if (loc && msg) return `${loc}: ${msg}` | |
| return msg || loc || '' | |
| }) | |
| .filter(Boolean) | |
| .join(' | ') | |
| : String(detail?.message || detail?.detail || 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 }), | |
| pesquisarLogradourosEixos: (limite = 20000) => getJson(`/api/pesquisa/logradouros-eixos?limite=${encodeURIComponent(String(limite))}`), | |
| 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') | |
| }, | |
| pesquisarLocalizacaoAvaliando(payload = {}) { | |
| return postJson('/api/pesquisa/localizar-avaliando', payload) | |
| }, | |
| pesquisarMapaModelos( | |
| modelosIds = [], | |
| avaliando = null, | |
| modoExibicao = 'pontos', | |
| criterioEspacial = 'maior_distancia', | |
| trabalhosTecnicosModelosModo = 'selecionados', | |
| trabalhosTecnicosProximidadeModo = 'sem_proximidade', | |
| trabalhosTecnicosRaioM = 1000, | |
| agruparPontosMercado = false, | |
| ) { | |
| const avaliandos = Array.isArray(avaliando) ? avaliando : [] | |
| const avaliandoUnico = !avaliandos.length && avaliando && typeof avaliando === 'object' ? avaliando : null | |
| return postJson('/api/pesquisa/mapa-modelos', { | |
| modelos_ids: modelosIds, | |
| avaliando_lat: avaliandoUnico?.lat ?? null, | |
| avaliando_lon: avaliandoUnico?.lon ?? null, | |
| avaliandos: avaliandos.length ? avaliandos : null, | |
| modo_exibicao: modoExibicao, | |
| criterio_espacial: criterioEspacial, | |
| trabalhos_tecnicos_modelos_modo: trabalhosTecnicosModelosModo, | |
| trabalhos_tecnicos_proximidade_modo: trabalhosTecnicosProximidadeModo, | |
| trabalhos_tecnicos_raio_m: trabalhosTecnicosRaioM, | |
| agrupar_pontos_mercado: Boolean(agruparPontosMercado), | |
| }) | |
| }, | |
| 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 }), | |
| pasteElaboracaoTable: (sessionId, pastedText) => postJson('/api/elaboracao/paste-table', { session_id: sessionId, pasted_text: pastedText }), | |
| confirmSheet: (sessionId, sheetName) => postJson('/api/elaboracao/confirm-sheet', { session_id: sessionId, sheet_name: sheetName }), | |
| applyImportColumns: (sessionId, colunas) => postJson('/api/elaboracao/apply-import-columns', { session_id: sessionId, colunas }), | |
| 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, options = {}) => postJson('/api/elaboracao/dispersao-interativo', { | |
| session_id: sessionId, | |
| alvo, | |
| ...(options || {}), | |
| }), | |
| 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 = null, indiceAvaliacao = null) => postJson('/api/elaboracao/evaluation/knn-details', { | |
| session_id: sessionId, | |
| valores_x: valoresX, | |
| indice_avaliacao: indiceAvaliacao, | |
| }), | |
| 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, observacaoModelo = '') => { | |
| 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, | |
| observacao_modelo: observacaoModelo, | |
| }), | |
| }) | |
| await handleResponse(response, path) | |
| return responseBlobWithDiagnostics(response, path) | |
| }, | |
| saveModelRepository: (sessionId, nomeArquivo, elaborador, observacaoModelo = '', confirmarSubstituicao = false) => postJson('/api/elaboracao/save-model-repository', { | |
| session_id: sessionId, | |
| nome_arquivo: nomeArquivo, | |
| elaborador, | |
| observacao_modelo: observacaoModelo, | |
| confirmar_substituicao: confirmarSubstituicao, | |
| }), | |
| exportBase: (sessionId, filtered = true) => getBlob(`/api/elaboracao/export-base?session_id=${encodeURIComponent(sessionId)}&filtered=${String(filtered)}`), | |
| updateElaboracaoMap: (sessionId, variavelMapa, modoMapa = 'pontos', pontoRaioMin = null, pontoRaioMax = null) => postJson('/api/elaboracao/map/update', { | |
| session_id: sessionId, | |
| variavel_mapa: variavelMapa, | |
| modo_mapa: modoMapa, | |
| ponto_raio_min: pontoRaioMin, | |
| ponto_raio_max: pontoRaioMax, | |
| }), | |
| updateElaboracaoResiduosMap: (sessionId, variavelMapa, modoMapa = 'pontos', escalaExtremoAbs = null, pontoRaioMin = null, pontoRaioMax = null) => postJson('/api/elaboracao/residuos/map/update', { | |
| session_id: sessionId, | |
| variavel_mapa: variavelMapa, | |
| modo_mapa: modoMapa, | |
| escala_extremo_abs: escalaExtremoAbs, | |
| ponto_raio_min: pontoRaioMin, | |
| ponto_raio_max: pontoRaioMax, | |
| }), | |
| 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 }), | |
| visualizacaoSection: (sessionId, secao, trabalhosTecnicosModelosModo = 'selecionados_e_outras_versoes') => postJson('/api/visualizacao/section', { | |
| session_id: sessionId, | |
| secao, | |
| trabalhos_tecnicos_modelos_modo: trabalhosTecnicosModelosModo, | |
| }), | |
| evaluationContextViz: (sessionId) => postJson('/api/visualizacao/evaluation/context', { session_id: sessionId }), | |
| updateVisualizacaoMap: (sessionId, variavelMapa, trabalhosTecnicosModelosModo = 'selecionados_e_outras_versoes', pontoRaioMin = null, pontoRaioMax = null) => postJson('/api/visualizacao/map/update', { | |
| session_id: sessionId, | |
| variavel_mapa: variavelMapa, | |
| trabalhos_tecnicos_modelos_modo: trabalhosTecnicosModelosModo, | |
| ponto_raio_min: pontoRaioMin, | |
| ponto_raio_max: pontoRaioMax, | |
| }), | |
| evaluationCalculateViz: (sessionId, valoresX, indiceBase, avaliando = null) => postJson('/api/visualizacao/evaluation/calculate', { | |
| session_id: sessionId, | |
| valores_x: valoresX, | |
| indice_base: indiceBase, | |
| avaliando_lat: avaliando?.lat ?? null, | |
| avaliando_lon: avaliando?.lon ?? null, | |
| }), | |
| evaluationKnnDetailsViz: (sessionId, valoresX, avaliando = null) => postJson('/api/visualizacao/evaluation/knn-details', { | |
| session_id: sessionId, | |
| valores_x: valoresX, | |
| avaliando_lat: avaliando?.lat ?? null, | |
| avaliando_lon: avaliando?.lon ?? null, | |
| }), | |
| evaluationLocationMapViz: (sessionId, avaliando = null) => postJson('/api/visualizacao/evaluation/location-map', { | |
| session_id: sessionId, | |
| avaliando_lat: avaliando?.lat ?? null, | |
| avaliando_lon: avaliando?.lon ?? null, | |
| }), | |
| exportEquationViz: (sessionId, mode = 'excel') => getBlob(`/api/visualizacao/equation/export?session_id=${encodeURIComponent(sessionId)}&mode=${encodeURIComponent(String(mode || 'excel'))}`), | |
| anexosModelos: () => getJson('/api/anexos/modelos'), | |
| anexosModeloColunas: (modeloId) => getJson(`/api/anexos/modelos/${encodeURIComponent(String(modeloId || ''))}/columns`), | |
| anexosGerar: (modeloId, bancoDadosColunas, avaliacao = null) => postJson('/api/anexos/generate', { | |
| modelo_id: modeloId, | |
| banco_dados_colunas: bancoDadosColunas, | |
| avaliacao, | |
| }), | |
| anexosDownload: (filename) => getBlob(`/api/anexos/download/${encodeURIComponent(String(filename || ''))}`), | |
| 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 }), | |
| trabalhosTecnicosListar: ({ pagina = 1, tamanhoPagina = 15, texto = '', tipo = '', ano = '', incluirIds = false } = {}) => { | |
| const params = new URLSearchParams({ pagina: String(pagina), tamanho_pagina: String(tamanhoPagina) }) | |
| if (texto) params.set('texto', texto) | |
| if (tipo) params.set('tipo', tipo) | |
| if (ano) params.set('ano', ano) | |
| if (incluirIds) params.set('incluir_ids', 'true') | |
| return getJson(`/api/trabalhos-tecnicos?${params.toString()}`) | |
| }, | |
| trabalhosTecnicosMapa: (trabalhosIds = null, modoExibicao = 'clusterizado', avaliando = null) => postJson('/api/trabalhos-tecnicos/mapa', { | |
| trabalhos_ids: trabalhosIds, | |
| modo_exibicao: modoExibicao, | |
| avaliando_lat: avaliando?.lat ?? null, | |
| avaliando_lon: avaliando?.lon ?? null, | |
| }), | |
| trabalhosTecnicosDetalhe: (trabalhoId) => postJson('/api/trabalhos-tecnicos/detalhe', { trabalho_id: trabalhoId }), | |
| trabalhosTecnicosCadastrar: (payload = {}) => postJson('/api/trabalhos-tecnicos/cadastrar', payload), | |
| trabalhosTecnicosSalvar: (payload = {}) => postJson('/api/trabalhos-tecnicos/salvar', payload), | |
| trabalhosTecnicosExcluir: (trabalhoId, confirmacao) => postJson('/api/trabalhos-tecnicos/excluir', { trabalho_id: trabalhoId, confirmacao }), | |
| trabalhosTecnicosModelosMl: () => getJson('/api/trabalhos-tecnicos/modelos-ml'), | |
| trabalhosTecnicosCadastrarModeloMl: (nome) => postJson('/api/trabalhos-tecnicos/modelos-ml', { nome }), | |
| 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') | |
| }, | |
| } | |