mesa-react / backend /app /services /serializers.py
Guilherme Silberfarb Costa
alteracoes na pesquisa
97e9b7f
from __future__ import annotations
import math
from pathlib import Path
from typing import Any
import numpy as np
import pandas as pd
COORD_COLUMN_NAMES = {
"lat",
"latitude",
"siat_latitude",
"lon",
"long",
"longitude",
"siat_longitude",
}
def _is_coordinate_column(col_name: Any) -> bool:
return str(col_name).strip().lower() in COORD_COLUMN_NAMES
def sanitize_value(value: Any) -> Any:
if isinstance(value, np.ndarray):
return [sanitize_value(item) for item in value.tolist()]
if isinstance(value, np.generic):
return sanitize_value(value.item())
if isinstance(value, (np.floating, float)):
if math.isnan(value) or math.isinf(value):
return None
return float(value)
if isinstance(value, (np.integer, int)):
return int(value)
if isinstance(value, (np.bool_, bool)):
return bool(value)
if isinstance(value, pd.Timestamp):
return value.isoformat()
if isinstance(value, pd.Timedelta):
return str(value)
if isinstance(value, pd.Series):
return [sanitize_value(item) for item in value.tolist()]
if isinstance(value, pd.Index):
return [sanitize_value(item) for item in value.tolist()]
if isinstance(value, pd.DataFrame):
return sanitize_value(value.to_dict(orient="records"))
if isinstance(value, Path):
return str(value)
if value is None:
return None
if isinstance(value, str):
return value
if isinstance(value, dict):
return {str(k): sanitize_value(v) for k, v in value.items()}
if isinstance(value, (list, tuple, set)):
return [sanitize_value(v) for v in value]
try:
if pd.isna(value):
return None
except Exception:
pass
return value
def dataframe_to_payload(
df: pd.DataFrame | None,
decimals: int | None = None,
max_rows: int | None = None,
) -> dict[str, Any] | None:
if df is None:
return None
# Evita cópia quando não há transformação necessária.
df_work = df if decimals is None else df.copy()
if decimals is not None:
numeric_cols = [
col
for col in df_work.select_dtypes(include=[np.number]).columns
if not _is_coordinate_column(col)
]
if numeric_cols:
df_work.loc[:, numeric_cols] = df_work.loc[:, numeric_cols].round(decimals)
total_rows = len(df_work)
# Regra de integridade: payloads nunca devem suprimir linhas.
truncated = False
columns = [str(c) for c in df_work.columns]
rows: list[dict[str, Any]] = []
# itertuples é significativamente mais leve que iterrows para payloads grandes.
for tuple_row in df_work.itertuples(index=True, name=None):
payload_row = {"_index": sanitize_value(tuple_row[0])}
for col, value in zip(columns, tuple_row[1:]):
payload_row[col] = sanitize_value(value)
rows.append(payload_row)
columns_payload = ["_index"] + columns
return {
"columns": columns_payload,
"rows": rows,
"total_rows": total_rows,
"returned_rows": len(rows),
"truncated": truncated,
}
def figure_to_payload(fig: Any) -> dict[str, Any] | None:
if fig is None:
return None
try:
return sanitize_value(fig.to_plotly_json())
except Exception:
return None