Spaces:
Running
Running
Guilherme Silberfarb Costa commited on
Commit ·
bc00a0d
1
Parent(s): 2ffc64b
Improve portable geospatial fallbacks and pesquisa loading
Browse files- backend/app/core/elaboracao/geocodificacao.py +25 -5
- backend/app/core/map_layers.py +34 -18
- backend/app/core/shapefile_runtime.py +107 -0
- backend/app/portable_launcher.py +2 -0
- backend/app/portable_runtime_log.py +30 -0
- backend/app/services/pesquisa_service.py +4 -0
- build/windows/mesa_frame_portable.spec +12 -2
- build/windows/smoke_test_portable.ps1 +12 -0
- frontend/src/components/PesquisaTab.jsx +13 -2
- frontend/src/styles.css +7 -0
backend/app/core/elaboracao/geocodificacao.py
CHANGED
|
@@ -11,6 +11,8 @@ import os
|
|
| 11 |
import numpy as np
|
| 12 |
import pandas as pd
|
| 13 |
|
|
|
|
|
|
|
| 14 |
from app.runtime_config import resolve_core_path
|
| 15 |
from .core import NOMES_LAT, NOMES_LON
|
| 16 |
|
|
@@ -64,18 +66,36 @@ def carregar_eixos():
|
|
| 64 |
if _gdf_eixos is not None:
|
| 65 |
return _gdf_eixos
|
| 66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
try:
|
| 68 |
import geopandas as gpd
|
| 69 |
except ImportError:
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
return None
|
| 72 |
|
| 73 |
-
if
|
| 74 |
-
|
| 75 |
return None
|
| 76 |
|
| 77 |
-
|
| 78 |
-
gdf = gdf.to_crs("EPSG:4326")
|
| 79 |
_gdf_eixos = gdf
|
| 80 |
return _gdf_eixos
|
| 81 |
|
|
|
|
| 11 |
import numpy as np
|
| 12 |
import pandas as pd
|
| 13 |
|
| 14 |
+
from app.core.shapefile_runtime import load_vector_dataframe
|
| 15 |
+
from app.portable_runtime_log import append_runtime_log
|
| 16 |
from app.runtime_config import resolve_core_path
|
| 17 |
from .core import NOMES_LAT, NOMES_LON
|
| 18 |
|
|
|
|
| 66 |
if _gdf_eixos is not None:
|
| 67 |
return _gdf_eixos
|
| 68 |
|
| 69 |
+
if not os.path.exists(_SHAPEFILE):
|
| 70 |
+
print(f"[geocodificacao] AVISO: shapefile não encontrado em {_SHAPEFILE}")
|
| 71 |
+
return None
|
| 72 |
+
|
| 73 |
try:
|
| 74 |
import geopandas as gpd
|
| 75 |
except ImportError:
|
| 76 |
+
gpd = None
|
| 77 |
+
|
| 78 |
+
if gpd is not None:
|
| 79 |
+
try:
|
| 80 |
+
gdf = gpd.read_file(_SHAPEFILE, engine="fiona")
|
| 81 |
+
gdf = gdf.to_crs("EPSG:4326")
|
| 82 |
+
_gdf_eixos = gdf
|
| 83 |
+
return _gdf_eixos
|
| 84 |
+
except Exception as exc:
|
| 85 |
+
append_runtime_log(f"[mesa] geocodificacao: falha no geopandas para eixos: {exc}")
|
| 86 |
+
|
| 87 |
+
try:
|
| 88 |
+
gdf = load_vector_dataframe(_SHAPEFILE, target_crs="EPSG:4326")
|
| 89 |
+
except Exception as exc:
|
| 90 |
+
print(f"[geocodificacao] AVISO: falha ao carregar eixos por fallback: {exc}")
|
| 91 |
+
append_runtime_log(f"[mesa] geocodificacao: fallback de eixos falhou: {exc}")
|
| 92 |
return None
|
| 93 |
|
| 94 |
+
if gdf.empty:
|
| 95 |
+
append_runtime_log("[mesa] geocodificacao: fallback de eixos retornou base vazia")
|
| 96 |
return None
|
| 97 |
|
| 98 |
+
append_runtime_log("[mesa] geocodificacao: usando fallback leve para eixos de logradouros")
|
|
|
|
| 99 |
_gdf_eixos = gdf
|
| 100 |
return _gdf_eixos
|
| 101 |
|
backend/app/core/map_layers.py
CHANGED
|
@@ -8,6 +8,8 @@ from typing import Any
|
|
| 8 |
|
| 9 |
import folium
|
| 10 |
from branca.element import Element
|
|
|
|
|
|
|
| 11 |
from app.runtime_config import resolve_core_path
|
| 12 |
|
| 13 |
_BAIRROS_SHP_PATH = resolve_core_path("dados", "Bairros_LC12112_16.shp")
|
|
@@ -45,29 +47,43 @@ def _carregar_bairros_geojson() -> dict[str, Any] | None:
|
|
| 45 |
return _BAIRROS_GEOJSON_CACHE
|
| 46 |
|
| 47 |
|
|
|
|
|
|
|
| 48 |
try:
|
| 49 |
import geopandas as gpd
|
| 50 |
except Exception:
|
| 51 |
-
|
| 52 |
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
geojson = None
|
| 57 |
-
else:
|
| 58 |
-
if gdf.crs is not None:
|
| 59 |
-
gdf = gdf.to_crs("EPSG:4326")
|
| 60 |
-
campos = ["geometry"]
|
| 61 |
-
for campo in _TOOLTIP_FIELDS:
|
| 62 |
-
if campo in gdf.columns:
|
| 63 |
-
campos.insert(0, campo)
|
| 64 |
-
break
|
| 65 |
-
gdf = gdf.loc[:, campos].copy()
|
| 66 |
-
if _SIMPLIFY_TOLERANCE > 0:
|
| 67 |
-
gdf["geometry"] = gdf.geometry.simplify(_SIMPLIFY_TOLERANCE, preserve_topology=True)
|
| 68 |
-
geojson = json.loads(gdf.to_json(drop_id=True))
|
| 69 |
-
except Exception:
|
| 70 |
-
geojson = None
|
| 71 |
|
| 72 |
with _BAIRROS_CACHE_LOCK:
|
| 73 |
if geojson is not None:
|
|
|
|
| 8 |
|
| 9 |
import folium
|
| 10 |
from branca.element import Element
|
| 11 |
+
from app.core.shapefile_runtime import load_vector_geojson
|
| 12 |
+
from app.portable_runtime_log import append_runtime_log
|
| 13 |
from app.runtime_config import resolve_core_path
|
| 14 |
|
| 15 |
_BAIRROS_SHP_PATH = resolve_core_path("dados", "Bairros_LC12112_16.shp")
|
|
|
|
| 47 |
return _BAIRROS_GEOJSON_CACHE
|
| 48 |
|
| 49 |
|
| 50 |
+
geojson = None
|
| 51 |
+
|
| 52 |
try:
|
| 53 |
import geopandas as gpd
|
| 54 |
except Exception:
|
| 55 |
+
gpd = None
|
| 56 |
|
| 57 |
+
if gpd is not None:
|
| 58 |
+
try:
|
| 59 |
+
gdf = gpd.read_file(_BAIRROS_SHP_PATH, engine="fiona")
|
| 60 |
+
if gdf is not None and not gdf.empty:
|
| 61 |
+
if gdf.crs is not None:
|
| 62 |
+
gdf = gdf.to_crs("EPSG:4326")
|
| 63 |
+
campos = ["geometry"]
|
| 64 |
+
for campo in _TOOLTIP_FIELDS:
|
| 65 |
+
if campo in gdf.columns:
|
| 66 |
+
campos.insert(0, campo)
|
| 67 |
+
break
|
| 68 |
+
gdf = gdf.loc[:, campos].copy()
|
| 69 |
+
if _SIMPLIFY_TOLERANCE > 0:
|
| 70 |
+
gdf["geometry"] = gdf.geometry.simplify(_SIMPLIFY_TOLERANCE, preserve_topology=True)
|
| 71 |
+
geojson = json.loads(gdf.to_json(drop_id=True))
|
| 72 |
+
except Exception as exc:
|
| 73 |
+
append_runtime_log(f"[mesa] bairros: falha no geopandas para camada de bairros: {exc}")
|
| 74 |
+
|
| 75 |
+
if geojson is None:
|
| 76 |
+
try:
|
| 77 |
+
geojson = load_vector_geojson(
|
| 78 |
+
_BAIRROS_SHP_PATH,
|
| 79 |
+
target_crs="EPSG:4326",
|
| 80 |
+
property_fields=_TOOLTIP_FIELDS,
|
| 81 |
+
simplify_tolerance=_SIMPLIFY_TOLERANCE,
|
| 82 |
+
)
|
| 83 |
+
append_runtime_log("[mesa] bairros: usando fallback leve para camada de bairros")
|
| 84 |
+
except Exception as exc:
|
| 85 |
+
append_runtime_log(f"[mesa] bairros: fallback de bairros falhou: {exc}")
|
| 86 |
geojson = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
with _BAIRROS_CACHE_LOCK:
|
| 89 |
if geojson is not None:
|
backend/app/core/shapefile_runtime.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Any
|
| 5 |
+
|
| 6 |
+
import pandas as pd
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def _build_transformer(source_crs: Any, target_crs: str | None):
|
| 10 |
+
if not source_crs or not target_crs:
|
| 11 |
+
return None
|
| 12 |
+
|
| 13 |
+
try:
|
| 14 |
+
from pyproj import CRS, Transformer
|
| 15 |
+
except Exception:
|
| 16 |
+
return None
|
| 17 |
+
|
| 18 |
+
try:
|
| 19 |
+
source = CRS.from_user_input(source_crs)
|
| 20 |
+
target = CRS.from_user_input(target_crs)
|
| 21 |
+
except Exception:
|
| 22 |
+
return None
|
| 23 |
+
|
| 24 |
+
if source == target:
|
| 25 |
+
return None
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
return Transformer.from_crs(source, target, always_xy=True)
|
| 29 |
+
except Exception:
|
| 30 |
+
return None
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def load_vector_dataframe(shapefile_path: str | Path, *, target_crs: str | None = "EPSG:4326") -> pd.DataFrame:
|
| 34 |
+
import fiona
|
| 35 |
+
from shapely.geometry import shape
|
| 36 |
+
from shapely.ops import transform as shapely_transform
|
| 37 |
+
|
| 38 |
+
path = Path(shapefile_path).expanduser().resolve()
|
| 39 |
+
if not path.exists():
|
| 40 |
+
raise FileNotFoundError(f"Shapefile nao encontrado: {path}")
|
| 41 |
+
|
| 42 |
+
rows: list[dict[str, Any]] = []
|
| 43 |
+
with fiona.open(path) as source:
|
| 44 |
+
transformer = _build_transformer(source.crs_wkt or source.crs, target_crs)
|
| 45 |
+
for feature in source:
|
| 46 |
+
properties = dict(feature.get("properties") or {})
|
| 47 |
+
geometry_raw = feature.get("geometry")
|
| 48 |
+
geometry = None
|
| 49 |
+
if geometry_raw:
|
| 50 |
+
geometry = shape(geometry_raw)
|
| 51 |
+
if transformer is not None:
|
| 52 |
+
geometry = shapely_transform(transformer.transform, geometry)
|
| 53 |
+
properties["geometry"] = geometry
|
| 54 |
+
rows.append(properties)
|
| 55 |
+
|
| 56 |
+
return pd.DataFrame(rows)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def load_vector_geojson(
|
| 60 |
+
shapefile_path: str | Path,
|
| 61 |
+
*,
|
| 62 |
+
target_crs: str | None = "EPSG:4326",
|
| 63 |
+
property_fields: tuple[str, ...] | list[str] | None = None,
|
| 64 |
+
simplify_tolerance: float = 0.0,
|
| 65 |
+
) -> dict[str, Any]:
|
| 66 |
+
import fiona
|
| 67 |
+
from shapely.geometry import mapping, shape
|
| 68 |
+
from shapely.ops import transform as shapely_transform
|
| 69 |
+
|
| 70 |
+
path = Path(shapefile_path).expanduser().resolve()
|
| 71 |
+
if not path.exists():
|
| 72 |
+
raise FileNotFoundError(f"Shapefile nao encontrado: {path}")
|
| 73 |
+
|
| 74 |
+
feature_collection: dict[str, Any] = {
|
| 75 |
+
"type": "FeatureCollection",
|
| 76 |
+
"features": [],
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
wanted_fields = tuple(str(field).strip() for field in (property_fields or ()) if str(field).strip())
|
| 80 |
+
with fiona.open(path) as source:
|
| 81 |
+
transformer = _build_transformer(source.crs_wkt or source.crs, target_crs)
|
| 82 |
+
for feature in source:
|
| 83 |
+
properties_raw = dict(feature.get("properties") or {})
|
| 84 |
+
geometry_raw = feature.get("geometry")
|
| 85 |
+
if not geometry_raw:
|
| 86 |
+
continue
|
| 87 |
+
|
| 88 |
+
geometry = shape(geometry_raw)
|
| 89 |
+
if transformer is not None:
|
| 90 |
+
geometry = shapely_transform(transformer.transform, geometry)
|
| 91 |
+
if simplify_tolerance and geometry is not None:
|
| 92 |
+
geometry = geometry.simplify(float(simplify_tolerance), preserve_topology=True)
|
| 93 |
+
|
| 94 |
+
properties = (
|
| 95 |
+
{field: properties_raw.get(field) for field in wanted_fields if field in properties_raw}
|
| 96 |
+
if wanted_fields
|
| 97 |
+
else properties_raw
|
| 98 |
+
)
|
| 99 |
+
feature_collection["features"].append(
|
| 100 |
+
{
|
| 101 |
+
"type": "Feature",
|
| 102 |
+
"properties": properties,
|
| 103 |
+
"geometry": mapping(geometry),
|
| 104 |
+
}
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
return feature_collection
|
backend/app/portable_launcher.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
|
|
|
| 3 |
import socket
|
| 4 |
import threading
|
| 5 |
import time
|
|
@@ -92,6 +93,7 @@ def main() -> int:
|
|
| 92 |
if settings.config_path is not None:
|
| 93 |
_append_startup_log(f"[mesa] configuracao carregada de {settings.config_path}", log_path)
|
| 94 |
_append_startup_log(f"[mesa] runtime local em {settings.runtime_dir}", log_path)
|
|
|
|
| 95 |
server, url = _build_server(settings, log_path)
|
| 96 |
_append_startup_log(f"[mesa] iniciando servidor local em {url}", log_path)
|
| 97 |
except Exception:
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
+
import os
|
| 4 |
import socket
|
| 5 |
import threading
|
| 6 |
import time
|
|
|
|
| 93 |
if settings.config_path is not None:
|
| 94 |
_append_startup_log(f"[mesa] configuracao carregada de {settings.config_path}", log_path)
|
| 95 |
_append_startup_log(f"[mesa] runtime local em {settings.runtime_dir}", log_path)
|
| 96 |
+
os.environ["MESA_STARTUP_LOG_FILE"] = str(log_path)
|
| 97 |
server, url = _build_server(settings, log_path)
|
| 98 |
_append_startup_log(f"[mesa] iniciando servidor local em {url}", log_path)
|
| 99 |
except Exception:
|
backend/app/portable_runtime_log.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def _log_path() -> Path | None:
|
| 8 |
+
raw = str(os.getenv("MESA_STARTUP_LOG_FILE") or "").strip()
|
| 9 |
+
if not raw:
|
| 10 |
+
return None
|
| 11 |
+
try:
|
| 12 |
+
return Path(raw).expanduser().resolve()
|
| 13 |
+
except Exception:
|
| 14 |
+
return None
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def append_runtime_log(message: str) -> None:
|
| 18 |
+
text = str(message).rstrip()
|
| 19 |
+
print(text, flush=True)
|
| 20 |
+
|
| 21 |
+
path = _log_path()
|
| 22 |
+
if path is None:
|
| 23 |
+
return
|
| 24 |
+
|
| 25 |
+
try:
|
| 26 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 27 |
+
with path.open("a", encoding="utf-8") as handle:
|
| 28 |
+
handle.write(f"{text}\n")
|
| 29 |
+
except Exception:
|
| 30 |
+
pass
|
backend/app/services/pesquisa_service.py
CHANGED
|
@@ -26,6 +26,7 @@ from app.core.map_layers import (
|
|
| 26 |
add_trabalhos_tecnicos_markers,
|
| 27 |
add_zoom_responsive_circle_markers,
|
| 28 |
)
|
|
|
|
| 29 |
from app.services import model_repository, trabalhos_tecnicos_service
|
| 30 |
from app.services.serializers import sanitize_value
|
| 31 |
|
|
@@ -2567,6 +2568,7 @@ def _carregar_catalogo_vias() -> list[dict[str, Any]]:
|
|
| 2567 |
|
| 2568 |
gdf = geocodificacao.carregar_eixos()
|
| 2569 |
if gdf is None or gdf.empty:
|
|
|
|
| 2570 |
raise HTTPException(status_code=500, detail="Base de eixos indisponivel para localizar o avaliando")
|
| 2571 |
|
| 2572 |
cols = {str(col).upper(): col for col in gdf.columns}
|
|
@@ -2575,6 +2577,7 @@ def _carregar_catalogo_vias() -> list[dict[str, Any]]:
|
|
| 2575 |
prefixo_col = cols.get("NMIDEPRE")
|
| 2576 |
abreviado_col = cols.get("NMIDEABR")
|
| 2577 |
if cdlog_col is None or nome_col is None:
|
|
|
|
| 2578 |
raise HTTPException(status_code=500, detail="Base de eixos sem colunas necessarias para localizar o avaliando")
|
| 2579 |
|
| 2580 |
vistos: set[int] = set()
|
|
@@ -2597,6 +2600,7 @@ def _carregar_catalogo_vias() -> list[dict[str, Any]]:
|
|
| 2597 |
}
|
| 2598 |
)
|
| 2599 |
_CATALOGO_VIAS_CACHE = list(catalogo)
|
|
|
|
| 2600 |
return list(catalogo)
|
| 2601 |
|
| 2602 |
|
|
|
|
| 26 |
add_trabalhos_tecnicos_markers,
|
| 27 |
add_zoom_responsive_circle_markers,
|
| 28 |
)
|
| 29 |
+
from app.portable_runtime_log import append_runtime_log
|
| 30 |
from app.services import model_repository, trabalhos_tecnicos_service
|
| 31 |
from app.services.serializers import sanitize_value
|
| 32 |
|
|
|
|
| 2568 |
|
| 2569 |
gdf = geocodificacao.carregar_eixos()
|
| 2570 |
if gdf is None or gdf.empty:
|
| 2571 |
+
append_runtime_log("[mesa] pesquisa: base de eixos indisponivel para catalogo de logradouros")
|
| 2572 |
raise HTTPException(status_code=500, detail="Base de eixos indisponivel para localizar o avaliando")
|
| 2573 |
|
| 2574 |
cols = {str(col).upper(): col for col in gdf.columns}
|
|
|
|
| 2577 |
prefixo_col = cols.get("NMIDEPRE")
|
| 2578 |
abreviado_col = cols.get("NMIDEABR")
|
| 2579 |
if cdlog_col is None or nome_col is None:
|
| 2580 |
+
append_runtime_log("[mesa] pesquisa: colunas obrigatorias dos eixos nao foram encontradas")
|
| 2581 |
raise HTTPException(status_code=500, detail="Base de eixos sem colunas necessarias para localizar o avaliando")
|
| 2582 |
|
| 2583 |
vistos: set[int] = set()
|
|
|
|
| 2600 |
}
|
| 2601 |
)
|
| 2602 |
_CATALOGO_VIAS_CACHE = list(catalogo)
|
| 2603 |
+
append_runtime_log(f"[mesa] pesquisa: catalogo de logradouros carregado com {len(catalogo)} vias")
|
| 2604 |
return list(catalogo)
|
| 2605 |
|
| 2606 |
|
build/windows/mesa_frame_portable.spec
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
|
| 3 |
from pathlib import Path
|
| 4 |
|
| 5 |
-
from PyInstaller.utils.hooks import collect_data_files, collect_submodules
|
| 6 |
|
| 7 |
|
| 8 |
PROJECT_ROOT = Path.cwd().resolve()
|
|
@@ -18,9 +18,19 @@ if LOCAL_DATA_DIR.exists():
|
|
| 18 |
datas += collect_data_files("safehttpx")
|
| 19 |
datas += collect_data_files("gradio")
|
| 20 |
datas += collect_data_files("gradio_client")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
hiddenimports = []
|
| 23 |
hiddenimports += collect_submodules("app")
|
|
|
|
|
|
|
|
|
|
| 24 |
hiddenimports += [
|
| 25 |
"uvicorn.loops.auto",
|
| 26 |
"uvicorn.protocols.http.auto",
|
|
@@ -31,7 +41,7 @@ hiddenimports += [
|
|
| 31 |
a = Analysis(
|
| 32 |
[str(PROJECT_ROOT / "backend" / "portable_app.py")],
|
| 33 |
pathex=[str(BACKEND_DIR)],
|
| 34 |
-
binaries=
|
| 35 |
datas=datas,
|
| 36 |
hiddenimports=hiddenimports,
|
| 37 |
hookspath=[],
|
|
|
|
| 2 |
|
| 3 |
from pathlib import Path
|
| 4 |
|
| 5 |
+
from PyInstaller.utils.hooks import collect_data_files, collect_dynamic_libs, collect_submodules
|
| 6 |
|
| 7 |
|
| 8 |
PROJECT_ROOT = Path.cwd().resolve()
|
|
|
|
| 18 |
datas += collect_data_files("safehttpx")
|
| 19 |
datas += collect_data_files("gradio")
|
| 20 |
datas += collect_data_files("gradio_client")
|
| 21 |
+
datas += collect_data_files("fiona")
|
| 22 |
+
datas += collect_data_files("geopandas")
|
| 23 |
+
datas += collect_data_files("pyproj")
|
| 24 |
+
|
| 25 |
+
binaries = []
|
| 26 |
+
binaries += collect_dynamic_libs("fiona")
|
| 27 |
+
binaries += collect_dynamic_libs("pyproj")
|
| 28 |
|
| 29 |
hiddenimports = []
|
| 30 |
hiddenimports += collect_submodules("app")
|
| 31 |
+
hiddenimports += collect_submodules("fiona")
|
| 32 |
+
hiddenimports += collect_submodules("pyproj")
|
| 33 |
+
hiddenimports += collect_submodules("shapely")
|
| 34 |
hiddenimports += [
|
| 35 |
"uvicorn.loops.auto",
|
| 36 |
"uvicorn.protocols.http.auto",
|
|
|
|
| 41 |
a = Analysis(
|
| 42 |
[str(PROJECT_ROOT / "backend" / "portable_app.py")],
|
| 43 |
pathex=[str(BACKEND_DIR)],
|
| 44 |
+
binaries=binaries,
|
| 45 |
datas=datas,
|
| 46 |
hiddenimports=hiddenimports,
|
| 47 |
hookspath=[],
|
build/windows/smoke_test_portable.ps1
CHANGED
|
@@ -15,6 +15,8 @@ $rootUrl = "http://127.0.0.1:$Port"
|
|
| 15 |
$healthUrl = "$rootUrl/api/health"
|
| 16 |
$loginUrl = "$rootUrl/api/auth/login"
|
| 17 |
$meUrl = "$rootUrl/api/auth/me"
|
|
|
|
|
|
|
| 18 |
|
| 19 |
New-Item -ItemType Directory -Force -Path $configDir | Out-Null
|
| 20 |
New-Item -ItemType Directory -Force -Path $runtimeDir | Out-Null
|
|
@@ -78,6 +80,11 @@ try {
|
|
| 78 |
throw "Smoke test failed: root path returned status $($rootResp.StatusCode)."
|
| 79 |
}
|
| 80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
$loginBody = @{
|
| 82 |
usuario = "smoke"
|
| 83 |
matricula = "123456"
|
|
@@ -93,6 +100,11 @@ try {
|
|
| 93 |
if ($meResp.usuario.usuario -ne "smoke") {
|
| 94 |
throw "Smoke test failed: /api/auth/me returned unexpected user."
|
| 95 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
}
|
| 97 |
finally {
|
| 98 |
if ($proc -and -not $proc.HasExited) {
|
|
|
|
| 15 |
$healthUrl = "$rootUrl/api/health"
|
| 16 |
$loginUrl = "$rootUrl/api/auth/login"
|
| 17 |
$meUrl = "$rootUrl/api/auth/me"
|
| 18 |
+
$logoUrl = "$rootUrl/logo_mesa.png"
|
| 19 |
+
$logradourosUrl = "$rootUrl/api/pesquisa/logradouros-eixos?limite=50"
|
| 20 |
|
| 21 |
New-Item -ItemType Directory -Force -Path $configDir | Out-Null
|
| 22 |
New-Item -ItemType Directory -Force -Path $runtimeDir | Out-Null
|
|
|
|
| 80 |
throw "Smoke test failed: root path returned status $($rootResp.StatusCode)."
|
| 81 |
}
|
| 82 |
|
| 83 |
+
$logoResp = Invoke-WebRequest -Uri $logoUrl -UseBasicParsing -TimeoutSec 5
|
| 84 |
+
if ($logoResp.StatusCode -ne 200) {
|
| 85 |
+
throw "Smoke test failed: logo path returned status $($logoResp.StatusCode)."
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
$loginBody = @{
|
| 89 |
usuario = "smoke"
|
| 90 |
matricula = "123456"
|
|
|
|
| 100 |
if ($meResp.usuario.usuario -ne "smoke") {
|
| 101 |
throw "Smoke test failed: /api/auth/me returned unexpected user."
|
| 102 |
}
|
| 103 |
+
|
| 104 |
+
$logradourosResp = Invoke-RestMethod -Uri $logradourosUrl -Headers @{ "X-Auth-Token" = $token } -TimeoutSec 10
|
| 105 |
+
if (-not $logradourosResp.logradouros_eixos -or $logradourosResp.logradouros_eixos.Count -lt 1) {
|
| 106 |
+
throw "Smoke test failed: /api/pesquisa/logradouros-eixos returned no suggestions."
|
| 107 |
+
}
|
| 108 |
}
|
| 109 |
finally {
|
| 110 |
if ($proc -and -not $proc.HasExited) {
|
frontend/src/components/PesquisaTab.jsx
CHANGED
|
@@ -2029,8 +2029,18 @@ export default function PesquisaTab({
|
|
| 2029 |
</div>
|
| 2030 |
|
| 2031 |
<div className="row pesquisa-actions pesquisa-actions-primary">
|
| 2032 |
-
<button
|
| 2033 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2034 |
</button>
|
| 2035 |
<button type="button" onClick={() => void onLimparFiltros()} disabled={isPesquisaBusy}>
|
| 2036 |
Limpar filtros
|
|
@@ -2285,6 +2295,7 @@ export default function PesquisaTab({
|
|
| 2285 |
</SectionBlock>
|
| 2286 |
</div>
|
| 2287 |
|
|
|
|
| 2288 |
<LoadingOverlay show={modeloAbertoLoading} label="Carregando modelo..." />
|
| 2289 |
</div>
|
| 2290 |
)
|
|
|
|
| 2029 |
</div>
|
| 2030 |
|
| 2031 |
<div className="row pesquisa-actions pesquisa-actions-primary">
|
| 2032 |
+
<button
|
| 2033 |
+
type="button"
|
| 2034 |
+
className={searchLoading ? 'is-loading' : ''}
|
| 2035 |
+
onClick={() => void buscarModelos()}
|
| 2036 |
+
disabled={isPesquisaBusy}
|
| 2037 |
+
>
|
| 2038 |
+
{searchLoading ? (
|
| 2039 |
+
<>
|
| 2040 |
+
<span className="repo-open-btn-spinner" aria-hidden="true" />
|
| 2041 |
+
<span>Pesquisando...</span>
|
| 2042 |
+
</>
|
| 2043 |
+
) : 'Pesquisar'}
|
| 2044 |
</button>
|
| 2045 |
<button type="button" onClick={() => void onLimparFiltros()} disabled={isPesquisaBusy}>
|
| 2046 |
Limpar filtros
|
|
|
|
| 2295 |
</SectionBlock>
|
| 2296 |
</div>
|
| 2297 |
|
| 2298 |
+
<LoadingOverlay show={searchLoading} label="Pesquisando modelos..." />
|
| 2299 |
<LoadingOverlay show={modeloAbertoLoading} label="Carregando modelo..." />
|
| 2300 |
</div>
|
| 2301 |
)
|
frontend/src/styles.css
CHANGED
|
@@ -2903,6 +2903,13 @@ button.pesquisa-coluna-remove:hover {
|
|
| 2903 |
justify-content: flex-end;
|
| 2904 |
}
|
| 2905 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2906 |
.pesquisa-mapa-actions {
|
| 2907 |
align-items: flex-end;
|
| 2908 |
gap: 12px;
|
|
|
|
| 2903 |
justify-content: flex-end;
|
| 2904 |
}
|
| 2905 |
|
| 2906 |
+
.pesquisa-actions-primary button.is-loading {
|
| 2907 |
+
display: inline-flex;
|
| 2908 |
+
align-items: center;
|
| 2909 |
+
justify-content: center;
|
| 2910 |
+
gap: 8px;
|
| 2911 |
+
}
|
| 2912 |
+
|
| 2913 |
.pesquisa-mapa-actions {
|
| 2914 |
align-items: flex-end;
|
| 2915 |
gap: 12px;
|