mesa-react / backend /app /api /elaboracao.py
Guilherme Silberfarb Costa
inclusao do botao salvar no repositorio e do campo de obsercaoes
275fd5b
from __future__ import annotations
import os
from typing import Any
from fastapi import APIRouter, File, Form, Request, UploadFile
from fastapi.responses import FileResponse
from pydantic import BaseModel, Field
from app.services import auth_service, elaboracao_service
from app.services.audit_log_service import log_event
from app.services.session_store import session_store
router = APIRouter(prefix="/api/elaboracao", tags=["elaboracao"])
class SessionPayload(BaseModel):
session_id: str
class ConfirmSheetPayload(SessionPayload):
sheet_name: str
class MapCoordsPayload(SessionPayload):
col_lat: str
col_lon: str
class GeocodePayload(SessionPayload):
col_cdlog: str
col_num: str
auto_200: bool = False
class CorrecaoGeo(BaseModel):
linha: int
cdlog_corrigido: str | None = None
numero_corrigido: str | None = None
class GeocodeCorrecaoPayload(SessionPayload):
correcoes: list[CorrecaoGeo] = Field(default_factory=list)
auto_200: bool = False
class ApplySelectionPayload(SessionPayload):
coluna_y: str
colunas_x: list[str]
tipo_y: str | None = None
coluna_area: str | None = None
dicotomicas: list[str] = Field(default_factory=list)
codigo_alocado: list[str] = Field(default_factory=list)
percentuais: list[str] = Field(default_factory=list)
outliers_anteriores: list[int] = Field(default_factory=list)
grau_min_coef: int = 0
grau_min_f: int = 0
class ClassificarXPayload(SessionPayload):
colunas_x: list[str] = Field(default_factory=list)
class SearchTransformPayload(SessionPayload):
grau_min_coef: int = 0
grau_min_f: int = 0
transformacoes_fixas: dict[str, str] = Field(default_factory=dict)
transformacao_y_fixa: str | None = None
class AdoptSuggestionPayload(SessionPayload):
indice: int
class FitModelPayload(SessionPayload):
transformacao_y: str
transformacoes_x: dict[str, str] = Field(default_factory=dict)
dicotomicas: list[str] = Field(default_factory=list)
codigo_alocado: list[str] = Field(default_factory=list)
percentuais: list[str] = Field(default_factory=list)
class DispersaoPayload(SessionPayload):
eixo_x: str = "transformado"
eixo_y_tipo: str = "y_transformado"
eixo_y_residuo: str | None = None
eixo_y_coluna: str | None = None
class DispersaoInterativoPayload(SessionPayload):
alvo: str = "secao10"
class DiagnosticoInterativoPayload(SessionPayload):
grafico: str = "obs_calc"
class TransformPreviewPayload(SessionPayload):
transformacao_y: str = "(x)"
transformacoes_x: dict[str, str] = Field(default_factory=dict)
class OutlierFiltro(BaseModel):
variavel: str
operador: str
valor: float
class OutlierFilterPayload(SessionPayload):
filtros: list[OutlierFiltro] = Field(default_factory=list)
class OutlierRestartPayload(SessionPayload):
outliers_texto: str | None = None
reincluir_texto: str | None = None
grau_min_coef: int = 3
grau_min_f: int = 3
class OutlierSummaryPayload(SessionPayload):
outliers_texto: str | None = None
reincluir_texto: str | None = None
class AvaliacaoPayload(SessionPayload):
valores_x: dict[str, Any]
indice_base: str | None = None
class AvaliacaoKnnDetalhesPayload(SessionPayload):
valores_x: dict[str, Any]
class AvaliacaoDeletePayload(SessionPayload):
indice: str | None = None
indice_base: str | None = None
class AvaliacaoBasePayload(SessionPayload):
indice_base: str | None = None
class ExportModeloPayload(SessionPayload):
nome_arquivo: str
elaborador: dict[str, Any] | None = None
observacao_modelo: str | None = None
class SalvarModeloRepositorioPayload(ExportModeloPayload):
confirmar_substituicao: bool = False
class UpdateMapaPayload(SessionPayload):
variavel_mapa: str | None = None
modo_mapa: str | None = None
escala_extremo_abs: float | None = None
class ColunaDataMercadoPayload(SessionPayload):
coluna_data: str
class RepositorioModeloPayload(SessionPayload):
modelo_id: str
@router.post("/upload")
async def upload_file(
session_id: str = Form(...),
file: UploadFile = File(...),
) -> dict[str, Any]:
session = session_store.get(session_id)
conteudo = await file.read()
elaboracao_service.save_uploaded_file(session, file.filename, conteudo)
return elaboracao_service.load_uploaded_file(session)
@router.get("/repositorio-modelos")
def repositorio_modelos(request: Request) -> dict[str, Any]:
user = auth_service.require_user(request)
resposta = elaboracao_service.listar_modelos_repositorio()
log_event(
"repositorio",
"listar_modelos_elaboracao",
user=user,
request=request,
details={"total_modelos": resposta.get("total_modelos")},
)
return resposta
@router.post("/repositorio-carregar")
def repositorio_carregar(payload: RepositorioModeloPayload, request: Request) -> dict[str, Any]:
session = session_store.get(payload.session_id)
user = auth_service.require_user(request)
resposta = elaboracao_service.carregar_modelo_repositorio(session, payload.modelo_id)
log_event(
"repositorio",
"carregar_modelo_elaboracao",
user=user,
session_id=payload.session_id,
request=request,
details={"modelo_id": payload.modelo_id},
)
return resposta
@router.post("/confirm-sheet")
def confirm_sheet(payload: ConfirmSheetPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.load_uploaded_file(session, selected_sheet=payload.sheet_name)
@router.post("/map-coords")
def map_coords(payload: MapCoordsPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.mapear_coordenadas_manualmente(session, payload.col_lat, payload.col_lon)
@router.post("/geocodificar")
def geocodificar(payload: GeocodePayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.geocodificar(session, payload.col_cdlog, payload.col_num, auto_200=payload.auto_200)
@router.post("/geocodificar-correcoes")
def geocodificar_correcoes(payload: GeocodeCorrecaoPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
correcoes = [item.model_dump() for item in payload.correcoes]
return elaboracao_service.aplicar_correcoes_geocodificacao(session, correcoes, auto_200=payload.auto_200)
@router.post("/geocodificar-reiniciar")
def geocodificar_reiniciar(payload: SessionPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.reiniciar_geocodificacao(session)
@router.post("/geocodificar-excluir-coords")
def geocodificar_excluir_coords(payload: SessionPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.excluir_coordenadas_para_geocodificacao(session)
@router.post("/apply-selection")
def apply_selection(payload: ApplySelectionPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.apply_selection(
session,
coluna_y=payload.coluna_y,
colunas_x=payload.colunas_x,
tipo_y=payload.tipo_y,
coluna_area=payload.coluna_area,
dicotomicas=payload.dicotomicas,
codigo_alocado=payload.codigo_alocado,
percentuais=payload.percentuais,
outliers_anteriores=payload.outliers_anteriores,
grau_min_coef=payload.grau_min_coef,
grau_min_f=payload.grau_min_f,
)
@router.post("/classify-x")
def classify_x(payload: ClassificarXPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.classificar_tipos_variaveis_x(session, payload.colunas_x)
@router.post("/search-transformations")
def search_transformations(payload: SearchTransformPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.search_transformacoes(
session,
grau_min_coef=payload.grau_min_coef,
grau_min_f=payload.grau_min_f,
transformacoes_fixas_usuario=payload.transformacoes_fixas,
transformacao_y_fixa_usuario=payload.transformacao_y_fixa,
)
@router.post("/adopt-suggestion")
def adopt_suggestion(payload: AdoptSuggestionPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.adotar_sugestao(session, payload.indice)
@router.post("/fit-model")
def fit_model(payload: FitModelPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.fit_model(
session,
transformacao_y=payload.transformacao_y,
transformacoes_x=payload.transformacoes_x,
dicotomicas=payload.dicotomicas,
codigo_alocado=payload.codigo_alocado,
percentuais=payload.percentuais,
)
@router.post("/model-dispersao")
def model_dispersao(payload: DispersaoPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.gerar_grafico_dispersao_modelo(
session,
eixo_x=payload.eixo_x,
eixo_y_tipo=payload.eixo_y_tipo,
eixo_y_residuo=payload.eixo_y_residuo,
eixo_y_coluna=payload.eixo_y_coluna,
)
@router.post("/dispersao-interativo")
def dispersao_interativo(payload: DispersaoInterativoPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.obter_grafico_dispersao_interativo(session, payload.alvo)
@router.post("/diagnostico-interativo")
def diagnostico_interativo(payload: DiagnosticoInterativoPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.obter_grafico_diagnostico_interativo(session, payload.grafico)
@router.post("/transform-preview")
def transform_preview(payload: TransformPreviewPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.preview_transformacao_manual(
session,
transformacao_y=payload.transformacao_y,
transformacoes_x=payload.transformacoes_x,
)
@router.post("/outliers/apply-filters")
def outliers_apply_filters(payload: OutlierFilterPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
filtros = [item.model_dump() for item in payload.filtros]
return elaboracao_service.apply_outlier_filters(session, filtros)
@router.post("/outliers/apply-filters-recursive")
def outliers_apply_filters_recursive(payload: OutlierFilterPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
filtros = [item.model_dump() for item in payload.filtros]
return elaboracao_service.apply_outlier_filters_recursive(session, filtros)
@router.post("/outliers/restart")
def outliers_restart(payload: OutlierRestartPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.reiniciar_iteracao(
session,
outliers_texto=payload.outliers_texto,
reincluir_texto=payload.reincluir_texto,
grau_min_coef=payload.grau_min_coef,
grau_min_f=payload.grau_min_f,
)
@router.post("/outliers/summary")
def outliers_summary(payload: OutlierSummaryPayload) -> dict[str, str]:
session = session_store.get(payload.session_id)
texto = elaboracao_service.resumir_outliers(
session.outliers_anteriores,
payload.outliers_texto,
payload.reincluir_texto,
)
return {"resumo": texto}
@router.post("/outliers/clear-history")
def outliers_clear_history(payload: SessionPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.limpar_historico_outliers(session)
@router.post("/evaluation/fields")
def evaluation_fields(payload: SessionPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return {"campos": elaboracao_service.build_campos_avaliacao(session)}
@router.post("/evaluation/calculate")
def evaluation_calculate(payload: AvaliacaoPayload, request: Request) -> dict[str, Any]:
session = session_store.get(payload.session_id)
user = auth_service.require_user(request)
resposta = elaboracao_service.calcular_avaliacao_elaboracao(session, payload.valores_x, payload.indice_base)
log_event(
"elaboracao",
"avaliacao_calcular",
user=user,
session_id=payload.session_id,
request=request,
details={"total_avaliacoes": len(resposta.get("avaliacoes") or [])},
)
return resposta
@router.post("/evaluation/knn-details")
def evaluation_knn_details(payload: AvaliacaoKnnDetalhesPayload, request: Request) -> dict[str, Any]:
session = session_store.get(payload.session_id)
user = auth_service.require_user(request)
resposta = elaboracao_service.detalhes_knn_avaliacao_elaboracao(session, payload.valores_x)
log_event(
"elaboracao",
"avaliacao_knn_detalhes",
user=user,
session_id=payload.session_id,
request=request,
)
return resposta
@router.post("/evaluation/clear")
def evaluation_clear(payload: SessionPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.limpar_avaliacoes_elaboracao(session)
@router.post("/evaluation/delete")
def evaluation_delete(payload: AvaliacaoDeletePayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.excluir_avaliacao_elaboracao(session, payload.indice, payload.indice_base)
@router.post("/evaluation/base")
def evaluation_base(payload: AvaliacaoBasePayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.atualizar_base_avaliacao_elaboracao(session, payload.indice_base)
@router.get("/evaluation/export")
def evaluation_export(session_id: str) -> FileResponse:
session = session_store.get(session_id)
caminho = elaboracao_service.exportar_avaliacoes_elaboracao(session)
return FileResponse(
path=caminho,
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
filename=os.path.basename(caminho),
)
@router.get("/equation/export")
def equation_export(session_id: str, mode: str = "excel") -> FileResponse:
session = session_store.get(session_id)
caminho, nome = elaboracao_service.exportar_equacao(session, mode)
return FileResponse(
path=caminho,
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
filename=nome,
)
@router.get("/avaliadores")
def listar_avaliadores() -> dict[str, Any]:
return {"avaliadores": elaboracao_service.list_avaliadores()}
@router.post("/export-model")
def export_model(payload: ExportModeloPayload, request: Request) -> FileResponse:
session = session_store.get(payload.session_id)
caminho, _ = elaboracao_service.exportar_modelo(
session,
payload.nome_arquivo,
elaborador=payload.elaborador,
observacao_modelo=payload.observacao_modelo,
)
user = auth_service.require_user(request)
log_event(
"elaboracao",
"exportar_modelo",
user=user,
session_id=payload.session_id,
request=request,
details={"nome_arquivo": os.path.basename(caminho)},
)
return FileResponse(
path=caminho,
media_type="application/octet-stream",
filename=os.path.basename(caminho),
)
@router.post("/save-model-repository")
def save_model_repository(payload: SalvarModeloRepositorioPayload, request: Request) -> dict[str, Any]:
session = session_store.get(payload.session_id)
user = auth_service.require_admin(request)
resposta = elaboracao_service.salvar_modelo_repositorio(
session,
payload.nome_arquivo,
elaborador=payload.elaborador,
observacao_modelo=payload.observacao_modelo,
actor=user.get("usuario"),
confirmar_substituicao=payload.confirmar_substituicao,
)
log_event(
"repositorio",
"salvar_modelo_repositorio_elaboracao",
user=user,
session_id=payload.session_id,
request=request,
details={
"nome_arquivo": payload.nome_arquivo,
"confirmar_substituicao": bool(payload.confirmar_substituicao),
"substituidos": resposta.get("resultado_upload", {}).get("substituidos", []),
},
)
return resposta
@router.get("/export-base")
def export_base(session_id: str, filtered: bool = True) -> FileResponse:
session = session_store.get(session_id)
caminho = elaboracao_service.exportar_base(session, usar_filtrado=filtered)
return FileResponse(
path=caminho,
media_type="text/csv",
filename=os.path.basename(caminho),
)
@router.post("/map/update")
def map_update(payload: UpdateMapaPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.atualizar_mapa(session, payload.variavel_mapa, payload.modo_mapa)
@router.post("/residuos/map/update")
def residuos_map_update(payload: UpdateMapaPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.atualizar_mapa_residuos(
session,
payload.variavel_mapa,
payload.modo_mapa,
payload.escala_extremo_abs,
)
@router.post("/market-date/preview")
def market_date_preview(payload: ColunaDataMercadoPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.previsualizar_coluna_data_mercado(session, payload.coluna_data)
@router.post("/market-date/apply")
def market_date_apply(payload: ColunaDataMercadoPayload) -> dict[str, Any]:
session = session_store.get(payload.session_id)
return elaboracao_service.aplicar_coluna_data_mercado(session, payload.coluna_data)
@router.get("/context")
def context(session_id: str) -> dict[str, Any]:
session = session_store.get(session_id)
return {
"coluna_y": session.coluna_y,
"tipo_y": session.tipo_y,
"coluna_area": session.coluna_area,
"colunas_area_candidatas": elaboracao_service.detectar_colunas_area(session.df_original) if session.df_original is not None else [],
"colunas_x": session.colunas_x,
"dicotomicas": session.dicotomicas,
"codigo_alocado": session.codigo_alocado,
"percentuais": session.percentuais,
"outliers_anteriores": session.outliers_anteriores,
"iteracao": session.iteracao,
"coluna_data_mercado": session.coluna_data_mercado,
"periodo_dados_mercado": {
"coluna_data": session.coluna_data_mercado,
"data_inicial": session.periodo_dados_mercado_inicio,
"data_final": session.periodo_dados_mercado_fim,
},
}