Spaces:
Running
Running
| from __future__ import annotations | |
| from pathlib import Path | |
| from typing import Any | |
| import numpy as np | |
| import pandas as pd | |
| from fastapi.testclient import TestClient | |
| from pydantic import TypeAdapter | |
| from app.core.visualizacao.map_payload import build_elaboracao_map_payload | |
| from app.main import app | |
| from app.models.session import SessionState | |
| from app.services import auth_service, elaboracao_service | |
| from app.services.session_store import session_store | |
| def _assert_no_numpy_scalars(value: Any, path: str = "root") -> None: | |
| assert not isinstance(value, np.generic), f"{path} contém {type(value).__name__}" | |
| if isinstance(value, dict): | |
| for key, item in value.items(): | |
| _assert_no_numpy_scalars(item, f"{path}.{key}") | |
| elif isinstance(value, (list, tuple)): | |
| for index, item in enumerate(value): | |
| _assert_no_numpy_scalars(item, f"{path}[{index}]") | |
| def _dump_as_fastapi_response(value: dict[str, Any]) -> bytes: | |
| return TypeAdapter(dict[str, Any]).dump_json(value) | |
| def _session(tmp_path: Path) -> SessionState: | |
| session = SessionState(session_id="teste-numpy", workdir=tmp_path) | |
| session.df_original = pd.DataFrame( | |
| { | |
| "Y": np.arange(10, dtype=float), | |
| "X": np.arange(10, dtype=float) + 1, | |
| "INDICE": pd.Series(np.arange(100, 110), dtype="int64"), | |
| "LATITUDE": np.linspace(-30.10, -30.19, 10), | |
| "LONGITUDE": np.linspace(-51.20, -51.29, 10), | |
| "FINALIDADE": ["TERRENO"] * 10, | |
| } | |
| ) | |
| session.df_filtrado = session.df_original.copy() | |
| session.coluna_y = "Y" | |
| session.colunas_x = ["X"] | |
| session.transformacoes_x = {"X": "(x)"} | |
| return session | |
| def test_map_payload_converts_numpy_indice_to_native_python() -> None: | |
| frame = pd.DataFrame( | |
| { | |
| "INDICE": pd.Series([101, 102, 103], dtype="int64"), | |
| "LATITUDE": [-30.10, -30.10, -30.11], | |
| "LONGITUDE": [-51.20, -51.20, -51.21], | |
| "FINALIDADE": ["TERRENO", "TERRENO", "TERRENO"], | |
| } | |
| ) | |
| payload = build_elaboracao_map_payload(frame) | |
| assert payload is not None | |
| mercado = next(layer for layer in payload["overlay_layers"] if layer["id"] == "mercado") | |
| ponto_simples = next(point for point in mercado["points"] if not point.get("grouped")) | |
| ponto_agrupado = next(point for point in mercado["points"] if point.get("grouped")) | |
| assert ponto_simples["indice"] == 103 | |
| assert type(ponto_simples["indice"]) is int | |
| assert [item["indice"] for item in ponto_agrupado["group_items"]] == [101, 102] | |
| assert all(type(item["indice"]) is int for item in ponto_agrupado["group_items"]) | |
| _assert_no_numpy_scalars(payload) | |
| assert _dump_as_fastapi_response(payload) | |
| def test_restart_outliers_normalizes_state_and_success_response(monkeypatch, tmp_path: Path) -> None: | |
| session = _session(tmp_path) | |
| session.outliers_anteriores = [np.int64(1)] # type: ignore[list-item] | |
| session.iteracao = np.int64(2) # type: ignore[assignment] | |
| monkeypatch.setattr(elaboracao_service, "_validar_outliers_micronumerosidade_geral", lambda *args, **kwargs: None) | |
| monkeypatch.setattr(elaboracao_service, "_atualizar_periodo_dados_mercado_filtrado", lambda *args, **kwargs: None) | |
| monkeypatch.setattr(elaboracao_service, "_montar_tabela_outliers_excluidos", lambda *args, **kwargs: None) | |
| monkeypatch.setattr(elaboracao_service, "ajustar_modelo", lambda *args, **kwargs: {}) | |
| monkeypatch.setattr( | |
| elaboracao_service, | |
| "apply_selection", | |
| lambda *args, **kwargs: {"valor_numpy_aninhado": {"indice": np.int64(7)}}, | |
| ) | |
| result = elaboracao_service.reiniciar_iteracao( | |
| session, | |
| outliers_texto="2", | |
| reincluir_texto="1", | |
| ) | |
| assert session.outliers_anteriores == [2] | |
| assert all(type(item) is int for item in session.outliers_anteriores) | |
| assert session.iteracao == 3 | |
| assert type(session.iteracao) is int | |
| assert result["valor_numpy_aninhado"]["indice"] == 7 | |
| _assert_no_numpy_scalars(result) | |
| assert _dump_as_fastapi_response(result) | |
| def test_restart_outliers_sanitizes_regression_failure_response(monkeypatch, tmp_path: Path) -> None: | |
| session = _session(tmp_path) | |
| session.outliers_anteriores = [np.int64(1)] # type: ignore[list-item] | |
| session.iteracao = np.int64(4) # type: ignore[assignment] | |
| monkeypatch.setattr(elaboracao_service, "_validar_outliers_micronumerosidade_geral", lambda *args, **kwargs: None) | |
| monkeypatch.setattr(elaboracao_service, "_atualizar_periodo_dados_mercado_filtrado", lambda *args, **kwargs: None) | |
| monkeypatch.setattr( | |
| elaboracao_service, | |
| "_montar_tabela_outliers_excluidos", | |
| lambda *args, **kwargs: {"rows": [{"indice": np.int64(1)}]}, | |
| ) | |
| monkeypatch.setattr(elaboracao_service, "ajustar_modelo", lambda *args, **kwargs: None) | |
| result = elaboracao_service.reiniciar_iteracao( | |
| session, | |
| outliers_texto="2", | |
| reincluir_texto=None, | |
| ) | |
| assert result["regressao_ok"] is False | |
| assert session.iteracao == 5 | |
| assert type(session.iteracao) is int | |
| _assert_no_numpy_scalars(result) | |
| assert _dump_as_fastapi_response(result) | |
| def test_outlier_restart_http_boundary_serializes_numpy(monkeypatch, tmp_path: Path) -> None: | |
| session = _session(tmp_path) | |
| monkeypatch.setattr(session_store, "get", lambda _session_id: session) | |
| monkeypatch.setattr(auth_service, "get_user_by_token", lambda _token: {"usuario": "teste"}) | |
| monkeypatch.setattr( | |
| elaboracao_service, | |
| "reiniciar_iteracao", | |
| lambda *args, **kwargs: { | |
| "iteracao": np.int64(2), | |
| "outliers_anteriores": [np.int64(101)], | |
| }, | |
| ) | |
| with TestClient(app) as client: | |
| response = client.post( | |
| "/api/elaboracao/outliers/restart", | |
| headers={"X-Auth-Token": "token-de-teste"}, | |
| json={"session_id": session.session_id}, | |
| ) | |
| assert response.status_code == 200 | |
| assert response.json() == {"iteracao": 2, "outliers_anteriores": [101]} | |