Spaces:
Running
Running
Guilherme Silberfarb Costa commited on
Commit ·
edfdca7
1
Parent(s): 1d89fc8
Add portable Windows build workflow
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .github/workflows/build-windows-portable.yml +51 -0
- .gitignore +7 -0
- README.md +48 -0
- backend/app/core/elaboracao/app.py +3 -1
- backend/app/core/elaboracao/formatadores.py +2 -1
- backend/app/core/elaboracao/geocodificacao.py +2 -2
- backend/app/core/map_layers.py +2 -2
- backend/app/core/pesquisa/modelos_dai/MOD_A_CCOM_Z2_004D.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_A_CCOM_Z2_005.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_A_CCOM_Z4_002N.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_A_DEP_Z1_Z2_Z3_Z4_003B.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_A_EDIF_Z1-Z2_001F.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_A_EDIF_Z1_005D.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_A_EDIF_Z1_Z2_001E.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_003F.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_004C.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_006B.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_007C.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_A_SALA_Z1_006B.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_A_SALA_Z1_006C.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_A_SALA_Z1_Z3_001.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_AP_Z1_011D.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_AP_Z1_020B.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_AP_Z1_022.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_EDIF_Z1_Z2_Z3_Z4_002E.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_RCOND_Z4_004.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_SALA_Z1_002E.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_TER_GENERICO_2016_2026_001.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z1_006L.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_008C.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_013C.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_013F.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_015C_PORTO_SECO.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_001H.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_001i.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_002A.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_002B.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z4_003J.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z4_016E.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z5_007C.dai +0 -3
- backend/app/core/pesquisa/modelos_dai/README.md +5 -0
- backend/app/core/visualizacao/app.py +20 -2
- backend/app/main.py +4 -1
- backend/app/portable_launcher.py +77 -0
- backend/app/runtime_config.py +176 -0
- backend/app/services/auth_service.py +4 -1
- backend/app/services/elaboracao_service.py +2 -1
- backend/app/services/model_repository.py +2 -1
- backend/app/services/session_store.py +7 -1
- backend/app/services/trabalhos_tecnicos_repository.py +2 -1
.github/workflows/build-windows-portable.yml
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: build-windows-portable
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
workflow_dispatch:
|
| 5 |
+
|
| 6 |
+
jobs:
|
| 7 |
+
build:
|
| 8 |
+
runs-on: windows-latest
|
| 9 |
+
timeout-minutes: 45
|
| 10 |
+
|
| 11 |
+
steps:
|
| 12 |
+
- name: Checkout
|
| 13 |
+
uses: actions/checkout@v4
|
| 14 |
+
|
| 15 |
+
- name: Setup Node
|
| 16 |
+
uses: actions/setup-node@v4
|
| 17 |
+
with:
|
| 18 |
+
node-version: 20
|
| 19 |
+
cache: npm
|
| 20 |
+
cache-dependency-path: frontend/package-lock.json
|
| 21 |
+
|
| 22 |
+
- name: Setup Python
|
| 23 |
+
uses: actions/setup-python@v5
|
| 24 |
+
with:
|
| 25 |
+
python-version: "3.12"
|
| 26 |
+
cache: pip
|
| 27 |
+
cache-dependency-path: backend/requirements.txt
|
| 28 |
+
|
| 29 |
+
- name: Build portable folder
|
| 30 |
+
shell: pwsh
|
| 31 |
+
run: ./build/windows/build_portable.ps1
|
| 32 |
+
|
| 33 |
+
- name: Smoke test executable
|
| 34 |
+
shell: pwsh
|
| 35 |
+
run: ./build/windows/smoke_test_portable.ps1
|
| 36 |
+
|
| 37 |
+
- name: Archive portable folder
|
| 38 |
+
shell: pwsh
|
| 39 |
+
run: |
|
| 40 |
+
if (Test-Path "dist/MesaFrame-portable.zip") {
|
| 41 |
+
Remove-Item "dist/MesaFrame-portable.zip" -Force
|
| 42 |
+
}
|
| 43 |
+
Compress-Archive -Path "dist/MesaFrame/*" -DestinationPath "dist/MesaFrame-portable.zip"
|
| 44 |
+
|
| 45 |
+
- name: Upload artifact
|
| 46 |
+
uses: actions/upload-artifact@v4
|
| 47 |
+
with:
|
| 48 |
+
name: MesaFrame-portable
|
| 49 |
+
path: |
|
| 50 |
+
dist/MesaFrame
|
| 51 |
+
dist/MesaFrame-portable.zip
|
.gitignore
CHANGED
|
@@ -8,6 +8,10 @@ __pycache__/
|
|
| 8 |
# Node
|
| 9 |
node_modules/
|
| 10 |
frontend/dist/
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
# System
|
| 13 |
.DS_Store
|
|
@@ -19,3 +23,6 @@ logs/**/*.jsonl
|
|
| 19 |
backend/local_data/*.sqlite3
|
| 20 |
backend/local_data/*.sqlite3-shm
|
| 21 |
backend/local_data/*.sqlite3-wal
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
# Node
|
| 9 |
node_modules/
|
| 10 |
frontend/dist/
|
| 11 |
+
/dist/
|
| 12 |
+
/build/*
|
| 13 |
+
!/build/windows/
|
| 14 |
+
!/build/windows/**
|
| 15 |
|
| 16 |
# System
|
| 17 |
.DS_Store
|
|
|
|
| 23 |
backend/local_data/*.sqlite3
|
| 24 |
backend/local_data/*.sqlite3-shm
|
| 25 |
backend/local_data/*.sqlite3-wal
|
| 26 |
+
|
| 27 |
+
# Local .dai model repository
|
| 28 |
+
backend/app/core/pesquisa/modelos_dai/*.dai
|
README.md
CHANGED
|
@@ -57,6 +57,8 @@ Para apontar para outro backend:
|
|
| 57 |
VITE_API_BASE=http://localhost:8000 npm run dev
|
| 58 |
```
|
| 59 |
|
|
|
|
|
|
|
| 60 |
## Repositório de modelos `.dai`
|
| 61 |
|
| 62 |
Os modelos usados em **Pesquisa**, **Elaboração** (carregar modelo existente) e
|
|
@@ -129,3 +131,49 @@ Comportamento por ambiente:
|
|
| 129 |
Variável opcional:
|
| 130 |
|
| 131 |
- `APP_LOGS_MODE` (`auto`/`enabled`/`disabled`) para forçar o modo de logs.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
VITE_API_BASE=http://localhost:8000 npm run dev
|
| 58 |
```
|
| 59 |
|
| 60 |
+
No build de producao servido pelo proprio backend, o frontend passa a usar a mesma origem (`/api`) por padrao.
|
| 61 |
+
|
| 62 |
## Repositório de modelos `.dai`
|
| 63 |
|
| 64 |
Os modelos usados em **Pesquisa**, **Elaboração** (carregar modelo existente) e
|
|
|
|
| 131 |
Variável opcional:
|
| 132 |
|
| 133 |
- `APP_LOGS_MODE` (`auto`/`enabled`/`disabled`) para forçar o modo de logs.
|
| 134 |
+
|
| 135 |
+
## Modo portátil Windows
|
| 136 |
+
|
| 137 |
+
Planejamento atual da distribuicao:
|
| 138 |
+
|
| 139 |
+
- o app continuara sendo desenvolvido normalmente no macOS;
|
| 140 |
+
- a pasta portatil de Windows sera gerada em um ambiente Windows;
|
| 141 |
+
- o artefato final sera uma pasta `MesaFrame/` gerada via `PyInstaller` em modo `onedir`;
|
| 142 |
+
- a configuracao externa fica em `config/appsettings.json`;
|
| 143 |
+
- o banco SQLite atual continua sendo reutilizado como esta;
|
| 144 |
+
- os dados compartilhados podem ficar em pasta de rede e o runtime temporario fica local na maquina do usuario.
|
| 145 |
+
|
| 146 |
+
Arquivos-base dessa estrategia:
|
| 147 |
+
|
| 148 |
+
- `backend/portable_app.py`: entrada do modo portatil
|
| 149 |
+
- `backend/app/portable_launcher.py`: sobe o backend local e abre o navegador
|
| 150 |
+
- `backend/app/runtime_config.py`: aplica a configuracao externa
|
| 151 |
+
- `build/windows/appsettings.example.json`: exemplo de configuracao
|
| 152 |
+
- `build/windows/build_portable.ps1`: script de build para ambiente Windows
|
| 153 |
+
- `build/windows/smoke_test_portable.ps1`: smoke test do executavel portatil
|
| 154 |
+
- `build/windows/mesa_frame_portable.spec`: spec inicial do PyInstaller
|
| 155 |
+
- `.github/workflows/build-windows-portable.yml`: workflow para gerar a pasta portatil em runner Windows
|
| 156 |
+
|
| 157 |
+
Fluxo recorrente de release:
|
| 158 |
+
|
| 159 |
+
1. desenvolver e validar normalmente no macOS
|
| 160 |
+
2. fazer push da branch que sera empacotada
|
| 161 |
+
3. disparar manualmente o workflow `.github/workflows/build-windows-portable.yml`
|
| 162 |
+
4. baixar o artefato `MesaFrame-portable`
|
| 163 |
+
5. copiar a pasta `MesaFrame/` para a rede
|
| 164 |
+
6. ajustar `MesaFrame/config/appsettings.json` para os caminhos compartilhados da operacao
|
| 165 |
+
7. no primeiro deploy, copiar manualmente a pasta de modelos `.dai` para o caminho configurado em `paths.models_dir`
|
| 166 |
+
|
| 167 |
+
Estrutura esperada do artefato:
|
| 168 |
+
|
| 169 |
+
- `MesaFrame/MesaFrame.exe`: executavel principal
|
| 170 |
+
- `MesaFrame/_internal/`: runtime embutido do Python e dependencias
|
| 171 |
+
- `MesaFrame/config/appsettings.example.json`: exemplo de configuracao externa
|
| 172 |
+
- `MesaFrame/config/appsettings.json`: configuracao efetiva da instalacao
|
| 173 |
+
- `MesaFrame/runtime/`: runtime temporario local usado nos testes automatizados
|
| 174 |
+
|
| 175 |
+
Observacao sobre GitHub e dados:
|
| 176 |
+
|
| 177 |
+
- o repositorio pode ficar somente com codigo e arquivos de suporte ao build
|
| 178 |
+
- os modelos `.dai` devem ficar fora do Git e ser copiados manualmente para a pasta compartilhada da operacao
|
| 179 |
+
- o `appsettings.json` publicado deve apontar `paths.models_dir` para essa pasta externa
|
backend/app/core/elaboracao/app.py
CHANGED
|
@@ -11,7 +11,9 @@ import pandas as pd
|
|
| 11 |
import os
|
| 12 |
import json
|
| 13 |
|
| 14 |
-
|
|
|
|
|
|
|
| 15 |
with open(_avaliadores_path, encoding="utf-8") as _f:
|
| 16 |
_avaliadores_raw = json.load(_f)
|
| 17 |
_avaliadores_lista = _avaliadores_raw.get("avaliadores", [])
|
|
|
|
| 11 |
import os
|
| 12 |
import json
|
| 13 |
|
| 14 |
+
from app.runtime_config import resolve_core_path
|
| 15 |
+
|
| 16 |
+
_avaliadores_path = resolve_core_path("elaboracao", "avaliadores.json")
|
| 17 |
with open(_avaliadores_path, encoding="utf-8") as _f:
|
| 18 |
_avaliadores_raw = json.load(_f)
|
| 19 |
_avaliadores_lista = _avaliadores_raw.get("avaliadores", [])
|
backend/app/core/elaboracao/formatadores.py
CHANGED
|
@@ -9,6 +9,7 @@ Sem dependência de Gradio.
|
|
| 9 |
import os
|
| 10 |
import numpy as np
|
| 11 |
import pandas as pd
|
|
|
|
| 12 |
|
| 13 |
|
| 14 |
# ============================================================
|
|
@@ -43,7 +44,7 @@ def arredondar_df(df, decimais=4):
|
|
| 43 |
|
| 44 |
def carregar_css():
|
| 45 |
"""Carrega CSS externo."""
|
| 46 |
-
css_path =
|
| 47 |
try:
|
| 48 |
with open(css_path, "r", encoding="utf-8") as f:
|
| 49 |
return f.read()
|
|
|
|
| 9 |
import os
|
| 10 |
import numpy as np
|
| 11 |
import pandas as pd
|
| 12 |
+
from app.runtime_config import resolve_core_path
|
| 13 |
|
| 14 |
|
| 15 |
# ============================================================
|
|
|
|
| 44 |
|
| 45 |
def carregar_css():
|
| 46 |
"""Carrega CSS externo."""
|
| 47 |
+
css_path = resolve_core_path("elaboracao", "styles.css")
|
| 48 |
try:
|
| 49 |
with open(css_path, "r", encoding="utf-8") as f:
|
| 50 |
return f.read()
|
backend/app/core/elaboracao/geocodificacao.py
CHANGED
|
@@ -11,14 +11,14 @@ import os
|
|
| 11 |
import numpy as np
|
| 12 |
import pandas as pd
|
| 13 |
|
|
|
|
| 14 |
from .core import NOMES_LAT, NOMES_LON
|
| 15 |
|
| 16 |
# ============================================================
|
| 17 |
# CAMINHO DO SHAPEFILE
|
| 18 |
# ============================================================
|
| 19 |
|
| 20 |
-
|
| 21 |
-
_SHAPEFILE = os.path.join(_BASE, "dados", "EixosLogradouros.shp")
|
| 22 |
|
| 23 |
# Cache em módulo — carregado uma vez por sessão
|
| 24 |
_gdf_eixos = None
|
|
|
|
| 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 |
|
| 17 |
# ============================================================
|
| 18 |
# CAMINHO DO SHAPEFILE
|
| 19 |
# ============================================================
|
| 20 |
|
| 21 |
+
_SHAPEFILE = str(resolve_core_path("dados", "EixosLogradouros.shp"))
|
|
|
|
| 22 |
|
| 23 |
# Cache em módulo — carregado uma vez por sessão
|
| 24 |
_gdf_eixos = None
|
backend/app/core/map_layers.py
CHANGED
|
@@ -8,9 +8,9 @@ from typing import Any
|
|
| 8 |
|
| 9 |
import folium
|
| 10 |
from branca.element import Element
|
|
|
|
| 11 |
|
| 12 |
-
|
| 13 |
-
_BAIRROS_SHP_PATH = _BASE_DIR / "dados" / "Bairros_LC12112_16.shp"
|
| 14 |
_TOOLTIP_FIELDS = ("NOME", "BAIRRO", "NME_BAI", "NOME_BAIRRO")
|
| 15 |
_SIMPLIFY_TOLERANCE = 0.00005
|
| 16 |
|
|
|
|
| 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")
|
|
|
|
| 14 |
_TOOLTIP_FIELDS = ("NOME", "BAIRRO", "NME_BAI", "NOME_BAIRRO")
|
| 15 |
_SIMPLIFY_TOLERANCE = 0.00005
|
| 16 |
|
backend/app/core/pesquisa/modelos_dai/MOD_A_CCOM_Z2_004D.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:ff221a8b0b6bec00a00f4c5ec0ff37429943328c943640fc98d3979074eb762c
|
| 3 |
-
size 61298
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_A_CCOM_Z2_005.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:32bccd0c76e37878c83697700c3cd1cb1fa2cf8a25b714ef8a3609b031377487
|
| 3 |
-
size 266364
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_A_CCOM_Z4_002N.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:92939746e3e2a2f1d455de0531c31b893c32415c22161478135321558a79ec8c
|
| 3 |
-
size 78623
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_A_DEP_Z1_Z2_Z3_Z4_003B.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:77ff58a2844f5c54863eb0b06d8530e48db9af48d5f2255e3ca12d119d19d232
|
| 3 |
-
size 105949
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_A_EDIF_Z1-Z2_001F.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:8053723e7c7e917e32709509bfb093ea718a65fec56ca095f0a99252cef69f75
|
| 3 |
-
size 237021
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_A_EDIF_Z1_005D.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:df52dbf6e9902c69cf770c2149548b2c13b7f637a6a696b467a49601e5b8a50d
|
| 3 |
-
size 62078
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_A_EDIF_Z1_Z2_001E.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:df8c18d84ecc169b41511e2743aca950680ebca0aadeee2b1978c2fd19716871
|
| 3 |
-
size 63789
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_003F.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:38580da29bd30d5c4cf5a20b008f3a852c6769f3915fa07ff46dbac4bf40be60
|
| 3 |
-
size 124431
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_004C.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:b4bdcc13ef212a014deda190957a7c30beb8f2b42cd3c2518ee9dd0323e87930
|
| 3 |
-
size 120063
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_006B.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:1f6a14fe402b1ec5b3be1492492e01fb0b9050a03e049e22cc80ebbf7b4f88d2
|
| 3 |
-
size 75212
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_007C.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:ce78dbb834e496a566e7d695a620ae96261ddc65f9fe397ab3ac0f65718f16e3
|
| 3 |
-
size 82416
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_A_SALA_Z1_006B.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:4d241fad2e540fa24a79edc7ea798ddd22fe456af0ee7e59465c138c10265d6f
|
| 3 |
-
size 122342
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_A_SALA_Z1_006C.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:b7e81622c34e2333ba1a628d7f275a8fa71912f7da8b9720f37298ac285faf0b
|
| 3 |
-
size 305073
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_A_SALA_Z1_Z3_001.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:d33b33df63a1d618ab2bcc26c5c111c698e8db9506d34622c0c1ed97fb18ad2c
|
| 3 |
-
size 44681
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_AP_Z1_011D.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:49c49b0a46dd373ae7d16307516fa748ae4acc540462570f0c5e567928a29383
|
| 3 |
-
size 5307946
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_AP_Z1_020B.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:f9f69d57ff5949afa8e43e6fe754b86b7d71098db740a70656db63ff003d46db
|
| 3 |
-
size 1999539
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_AP_Z1_022.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:17944f8132fce958a7168d0efc73f4aa07575c16993a16022f89c2d7596c05d0
|
| 3 |
-
size 1318594
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_EDIF_Z1_Z2_Z3_Z4_002E.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:7d64c816f183f6c18952eacf76fbce781d621d5b5ec5f4b51f40a85b0983e40d
|
| 3 |
-
size 1577576
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_RCOND_Z4_004.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:70e655434ec5441708a1d0f108ae7b8af702cfb7577110db59846c7b4685879e
|
| 3 |
-
size 836586
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_SALA_Z1_002E.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:a81098b6a7206d18f23e0676952fa60583283ea84293ea94ad0f84b1414a4210
|
| 3 |
-
size 4622543
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_GENERICO_2016_2026_001.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:30565ca1281c1abe3f224f7309e40479fa20224fe68fade4ce146f804e728c09
|
| 3 |
-
size 19220124
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z1_006L.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:47e009f401dd9644d030f90a0111ac4765bd1054a44027c2fb9f327fca7ad380
|
| 3 |
-
size 1906640
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_008C.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:ceaae08485539da6b97aded2086fa292a86c1f557cdd7875ba36f0b5f270d60c
|
| 3 |
-
size 824103
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_013C.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:46137a963d90e5697ef2e8c01ce5b56af6237ee426fc20662bbf99b14acb2743
|
| 3 |
-
size 1481702
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_013F.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:cc522cf1e32ed0a3ff25157492accde2c0bc87e2013a2ea8ed3387317317de36
|
| 3 |
-
size 2327452
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_015C_PORTO_SECO.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:d71651a7f845c893895bfd6b73c4209b597e1ba78b4c15c0924987f360c89108
|
| 3 |
-
size 3635197
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_001H.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:749c833d7cd4190ea7cd0453410c63701746c8ac34b76444f0be55970700b185
|
| 3 |
-
size 1731659
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_001i.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:fff0145b9990bb251401f85e5c1614fd84936801d6757482443987e1d880a099
|
| 3 |
-
size 1969416
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_002A.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:762b80e17c476218ceb1f146e816c6b7a68f0011f35ec27f0535970602c5ea64
|
| 3 |
-
size 1586112
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_002B.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:3468371b064f43da5b39d992bcccdb8d7c698e5f127e8040932e1bdf40ddcc1d
|
| 3 |
-
size 1998609
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z4_003J.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:63059cf719829b1ab76852e6078b4d2808b6da734fa780e429bd852d000a24f6
|
| 3 |
-
size 1559917
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z4_016E.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:ddbe47357238199b28515d42b6cc2730df5b9158b999431515947ae74cd73cdf
|
| 3 |
-
size 5780373
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z5_007C.dai
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:393b92b31396916b0cc500983649592f5ab0bdb6ed6d521b3abed2b9e5cf5235
|
| 3 |
-
size 1372051
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/core/pesquisa/modelos_dai/README.md
CHANGED
|
@@ -5,6 +5,11 @@ como `local` (`MODELOS_REPOSITORIO_PROVIDER=local`).
|
|
| 5 |
|
| 6 |
Coloque nesta pasta os arquivos `.dai` que devem aparecer na aba **Pesquisa**.
|
| 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
## Estrutura
|
| 9 |
|
| 10 |
- `NOME_MODELO.dai`
|
|
|
|
| 5 |
|
| 6 |
Coloque nesta pasta os arquivos `.dai` que devem aparecer na aba **Pesquisa**.
|
| 7 |
|
| 8 |
+
Quando o projeto for espelhado em um repositório GitHub para gerar o build
|
| 9 |
+
portátil, os arquivos `.dai` nao devem ser versionados. Nesse fluxo, eles podem
|
| 10 |
+
ser mantidos apenas localmente ou copiados manualmente para a pasta externa
|
| 11 |
+
configurada em `paths.models_dir` no `config/appsettings.json`.
|
| 12 |
+
|
| 13 |
## Estrutura
|
| 14 |
|
| 15 |
- `NOME_MODELO.dai`
|
backend/app/core/visualizacao/app.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
|
|
|
|
|
| 1 |
# ============================================================
|
| 2 |
# IMPORTAÇÕES
|
| 3 |
# ============================================================
|
| 4 |
-
import gradio as gr
|
| 5 |
import pandas as pd
|
| 6 |
import numpy as np
|
| 7 |
import folium
|
|
@@ -13,6 +14,23 @@ import traceback
|
|
| 13 |
from datetime import datetime
|
| 14 |
from html import escape
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
# Importações para gráficos (trazidas de graficos.py)
|
| 18 |
from scipy import stats
|
|
@@ -50,7 +68,7 @@ COR_LINHA = '#dc3545' # Vermelho para linhas de referência
|
|
| 50 |
# ============================================================
|
| 51 |
def carregar_css():
|
| 52 |
"""Carrega o arquivo CSS externo."""
|
| 53 |
-
css_path =
|
| 54 |
try:
|
| 55 |
with open(css_path, "r", encoding="utf-8") as f:
|
| 56 |
return f.read()
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
# ============================================================
|
| 4 |
# IMPORTAÇÕES
|
| 5 |
# ============================================================
|
|
|
|
| 6 |
import pandas as pd
|
| 7 |
import numpy as np
|
| 8 |
import folium
|
|
|
|
| 14 |
from datetime import datetime
|
| 15 |
from html import escape
|
| 16 |
|
| 17 |
+
from app.runtime_config import resolve_core_path
|
| 18 |
+
|
| 19 |
+
try:
|
| 20 |
+
import gradio as gr
|
| 21 |
+
except Exception: # pragma: no cover - runtime portatil nao precisa da UI gradio
|
| 22 |
+
class _GradioPlaceholder:
|
| 23 |
+
class SelectData:
|
| 24 |
+
pass
|
| 25 |
+
|
| 26 |
+
@staticmethod
|
| 27 |
+
def update(*args, **kwargs):
|
| 28 |
+
raise RuntimeError("Gradio indisponivel neste runtime")
|
| 29 |
+
|
| 30 |
+
def __getattr__(self, name: str):
|
| 31 |
+
raise RuntimeError(f"Gradio indisponivel neste runtime: {name}")
|
| 32 |
+
|
| 33 |
+
gr = _GradioPlaceholder()
|
| 34 |
|
| 35 |
# Importações para gráficos (trazidas de graficos.py)
|
| 36 |
from scipy import stats
|
|
|
|
| 68 |
# ============================================================
|
| 69 |
def carregar_css():
|
| 70 |
"""Carrega o arquivo CSS externo."""
|
| 71 |
+
css_path = resolve_core_path("visualizacao", "styles.css")
|
| 72 |
try:
|
| 73 |
with open(css_path, "r", encoding="utf-8") as f:
|
| 74 |
return f.read()
|
backend/app/main.py
CHANGED
|
@@ -8,9 +8,12 @@ from fastapi.responses import JSONResponse
|
|
| 8 |
from fastapi.staticfiles import StaticFiles
|
| 9 |
|
| 10 |
from app.api import auth, elaboracao, health, logs, pesquisa, repositorio, session, trabalhos_tecnicos, visualizacao
|
|
|
|
| 11 |
from app.services import auth_service
|
| 12 |
|
| 13 |
|
|
|
|
|
|
|
| 14 |
app = FastAPI(
|
| 15 |
title="MESA Frame API",
|
| 16 |
version="1.0.0",
|
|
@@ -58,7 +61,7 @@ app.include_router(logs.router)
|
|
| 58 |
|
| 59 |
|
| 60 |
def _mount_frontend_if_exists() -> None:
|
| 61 |
-
frontend_dist =
|
| 62 |
index_file = frontend_dist / "index.html"
|
| 63 |
if not index_file.exists():
|
| 64 |
return
|
|
|
|
| 8 |
from fastapi.staticfiles import StaticFiles
|
| 9 |
|
| 10 |
from app.api import auth, elaboracao, health, logs, pesquisa, repositorio, session, trabalhos_tecnicos, visualizacao
|
| 11 |
+
from app.runtime_config import apply_runtime_config, resolve_frontend_dist_dir
|
| 12 |
from app.services import auth_service
|
| 13 |
|
| 14 |
|
| 15 |
+
apply_runtime_config()
|
| 16 |
+
|
| 17 |
app = FastAPI(
|
| 18 |
title="MESA Frame API",
|
| 19 |
version="1.0.0",
|
|
|
|
| 61 |
|
| 62 |
|
| 63 |
def _mount_frontend_if_exists() -> None:
|
| 64 |
+
frontend_dist = resolve_frontend_dist_dir()
|
| 65 |
index_file = frontend_dist / "index.html"
|
| 66 |
if not index_file.exists():
|
| 67 |
return
|
backend/app/portable_launcher.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import socket
|
| 4 |
+
import threading
|
| 5 |
+
import time
|
| 6 |
+
import urllib.request
|
| 7 |
+
import webbrowser
|
| 8 |
+
|
| 9 |
+
import uvicorn
|
| 10 |
+
|
| 11 |
+
from app.runtime_config import RuntimeSettings, apply_runtime_config
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _first_available_port(host: str, preferred_port: int, max_attempts: int = 20) -> int:
|
| 15 |
+
for offset in range(max_attempts):
|
| 16 |
+
port = preferred_port + offset
|
| 17 |
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
| 18 |
+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
| 19 |
+
try:
|
| 20 |
+
sock.bind((host, port))
|
| 21 |
+
except OSError:
|
| 22 |
+
continue
|
| 23 |
+
return port
|
| 24 |
+
raise RuntimeError(f"Nao foi possivel reservar uma porta local a partir de {preferred_port}")
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def _wait_for_server_and_open_browser(url: str, timeout_s: float = 45.0) -> None:
|
| 28 |
+
health_url = f"{url.rstrip('/')}/api/health"
|
| 29 |
+
deadline = time.time() + timeout_s
|
| 30 |
+
while time.time() < deadline:
|
| 31 |
+
try:
|
| 32 |
+
with urllib.request.urlopen(health_url, timeout=1.5) as response:
|
| 33 |
+
if response.status == 200:
|
| 34 |
+
webbrowser.open(url)
|
| 35 |
+
return
|
| 36 |
+
except Exception:
|
| 37 |
+
time.sleep(0.5)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _build_server(settings: RuntimeSettings) -> tuple[uvicorn.Server, str]:
|
| 41 |
+
port = _first_available_port(settings.host, settings.port)
|
| 42 |
+
url = f"http://{settings.host}:{port}"
|
| 43 |
+
|
| 44 |
+
from app.main import app
|
| 45 |
+
|
| 46 |
+
config = uvicorn.Config(
|
| 47 |
+
app=app,
|
| 48 |
+
host=settings.host,
|
| 49 |
+
port=port,
|
| 50 |
+
reload=False,
|
| 51 |
+
log_level="info",
|
| 52 |
+
)
|
| 53 |
+
return uvicorn.Server(config), url
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def main() -> int:
|
| 57 |
+
settings = apply_runtime_config()
|
| 58 |
+
server, url = _build_server(settings)
|
| 59 |
+
|
| 60 |
+
print(f"[mesa] iniciando servidor local em {url}")
|
| 61 |
+
if settings.config_path is not None:
|
| 62 |
+
print(f"[mesa] configuracao carregada de {settings.config_path}")
|
| 63 |
+
print(f"[mesa] runtime local em {settings.runtime_dir}")
|
| 64 |
+
|
| 65 |
+
if settings.open_browser:
|
| 66 |
+
opener = threading.Thread(target=_wait_for_server_and_open_browser, args=(url,), daemon=True)
|
| 67 |
+
opener.start()
|
| 68 |
+
|
| 69 |
+
try:
|
| 70 |
+
server.run()
|
| 71 |
+
except KeyboardInterrupt:
|
| 72 |
+
print("[mesa] encerrando aplicativo")
|
| 73 |
+
return 0
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
if __name__ == "__main__":
|
| 77 |
+
raise SystemExit(main())
|
backend/app/runtime_config.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import os
|
| 5 |
+
import sys
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from typing import Any
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
_APPLIED_SETTINGS: "RuntimeSettings | None" = None
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@dataclass(frozen=True)
|
| 15 |
+
class RuntimeSettings:
|
| 16 |
+
config_path: Path | None
|
| 17 |
+
app_root: Path
|
| 18 |
+
runtime_dir: Path
|
| 19 |
+
host: str
|
| 20 |
+
port: int
|
| 21 |
+
open_browser: bool
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def resolve_app_root() -> Path:
|
| 25 |
+
if getattr(sys, "frozen", False):
|
| 26 |
+
return Path(sys.executable).resolve().parent
|
| 27 |
+
return Path(__file__).resolve().parents[2]
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def resolve_bundle_root() -> Path:
|
| 31 |
+
meipass = getattr(sys, "_MEIPASS", None)
|
| 32 |
+
if meipass:
|
| 33 |
+
return Path(str(meipass)).resolve()
|
| 34 |
+
if getattr(sys, "frozen", False):
|
| 35 |
+
return Path(sys.executable).resolve().parent
|
| 36 |
+
return resolve_app_root()
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def resolve_frontend_dist_dir() -> Path:
|
| 40 |
+
base = resolve_bundle_root() if getattr(sys, "frozen", False) else resolve_app_root()
|
| 41 |
+
return base / "frontend" / "dist"
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def resolve_core_path(*parts: str) -> Path:
|
| 45 |
+
if getattr(sys, "frozen", False):
|
| 46 |
+
return resolve_bundle_root().joinpath("app", "core", *parts)
|
| 47 |
+
return resolve_app_root().joinpath("backend", "app", "core", *parts)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def resolve_local_data_path(*parts: str) -> Path:
|
| 51 |
+
if getattr(sys, "frozen", False):
|
| 52 |
+
return resolve_bundle_root().joinpath("local_data", *parts)
|
| 53 |
+
return resolve_app_root().joinpath("backend", "local_data", *parts)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _default_runtime_dir() -> Path:
|
| 57 |
+
app_name = "MesaFrame"
|
| 58 |
+
if sys.platform == "win32":
|
| 59 |
+
base = Path(str(os.getenv("LOCALAPPDATA") or Path.home() / "AppData" / "Local"))
|
| 60 |
+
return base / app_name
|
| 61 |
+
if sys.platform == "darwin":
|
| 62 |
+
return Path.home() / "Library" / "Application Support" / app_name
|
| 63 |
+
return Path(str(os.getenv("XDG_STATE_HOME") or (Path.home() / ".local" / "state"))) / app_name
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def _expand_path(value: Any, base_dir: Path | None = None) -> Path | None:
|
| 67 |
+
text = str(value or "").strip()
|
| 68 |
+
if not text:
|
| 69 |
+
return None
|
| 70 |
+
expanded = os.path.expandvars(text)
|
| 71 |
+
path = Path(expanded).expanduser()
|
| 72 |
+
if not path.is_absolute():
|
| 73 |
+
anchor = base_dir or resolve_app_root()
|
| 74 |
+
path = anchor / path
|
| 75 |
+
return path.resolve()
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def _as_bool(value: Any, default: bool) -> bool:
|
| 79 |
+
if value is None:
|
| 80 |
+
return default
|
| 81 |
+
text = str(value).strip().lower()
|
| 82 |
+
if text in {"1", "true", "yes", "y", "on", "sim"}:
|
| 83 |
+
return True
|
| 84 |
+
if text in {"0", "false", "no", "n", "off", "nao", "não"}:
|
| 85 |
+
return False
|
| 86 |
+
return default
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def _as_int(value: Any, default: int) -> int:
|
| 90 |
+
if value is None:
|
| 91 |
+
return default
|
| 92 |
+
try:
|
| 93 |
+
parsed = int(str(value).strip())
|
| 94 |
+
except Exception:
|
| 95 |
+
return default
|
| 96 |
+
return parsed if parsed > 0 else default
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def _read_config_file(config_path: Path | None) -> dict[str, Any]:
|
| 100 |
+
if config_path is None or not config_path.exists():
|
| 101 |
+
return {}
|
| 102 |
+
try:
|
| 103 |
+
payload = json.loads(config_path.read_text(encoding="utf-8"))
|
| 104 |
+
except Exception as exc: # pragma: no cover - surfaced to caller in packaged runtime
|
| 105 |
+
raise RuntimeError(f"Falha ao ler arquivo de configuracao '{config_path}': {exc}") from exc
|
| 106 |
+
if not isinstance(payload, dict):
|
| 107 |
+
raise RuntimeError(f"Arquivo de configuracao invalido: '{config_path}'")
|
| 108 |
+
return payload
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def _default_config_path(app_root: Path) -> Path:
|
| 112 |
+
return app_root / "config" / "appsettings.json"
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def _set_env_if_value(key: str, value: Any) -> None:
|
| 116 |
+
text = str(value or "").strip()
|
| 117 |
+
if text:
|
| 118 |
+
os.environ[key] = text
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def apply_runtime_config(config_path: str | Path | None = None) -> RuntimeSettings:
|
| 122 |
+
global _APPLIED_SETTINGS
|
| 123 |
+
if _APPLIED_SETTINGS is not None:
|
| 124 |
+
return _APPLIED_SETTINGS
|
| 125 |
+
|
| 126 |
+
app_root = resolve_app_root()
|
| 127 |
+
config_source = config_path or os.getenv("MESA_APP_CONFIG") or _default_config_path(app_root)
|
| 128 |
+
config_file = _expand_path(config_source)
|
| 129 |
+
payload = _read_config_file(config_file)
|
| 130 |
+
config_base_dir = config_file.parent if config_file is not None else app_root
|
| 131 |
+
|
| 132 |
+
server_cfg = payload.get("server") if isinstance(payload.get("server"), dict) else {}
|
| 133 |
+
paths_cfg = payload.get("paths") if isinstance(payload.get("paths"), dict) else {}
|
| 134 |
+
env_cfg = payload.get("env") if isinstance(payload.get("env"), dict) else {}
|
| 135 |
+
|
| 136 |
+
runtime_dir = _expand_path(paths_cfg.get("runtime_dir"), base_dir=config_base_dir) or _default_runtime_dir()
|
| 137 |
+
runtime_dir.mkdir(parents=True, exist_ok=True)
|
| 138 |
+
(runtime_dir / "sessions").mkdir(parents=True, exist_ok=True)
|
| 139 |
+
(runtime_dir / "logs").mkdir(parents=True, exist_ok=True)
|
| 140 |
+
|
| 141 |
+
os.environ.setdefault("MESA_RUNTIME_DIR", str(runtime_dir))
|
| 142 |
+
|
| 143 |
+
models_dir = _expand_path(paths_cfg.get("models_dir"), base_dir=config_base_dir)
|
| 144 |
+
if models_dir is not None:
|
| 145 |
+
os.environ["MODELOS_REPOSITORIO_PROVIDER"] = "local"
|
| 146 |
+
os.environ["MODELOS_REPOSITORIO_LOCAL_DIR"] = str(models_dir)
|
| 147 |
+
|
| 148 |
+
trabalhos_db = _expand_path(paths_cfg.get("trabalhos_tecnicos_db"), base_dir=config_base_dir)
|
| 149 |
+
if trabalhos_db is not None:
|
| 150 |
+
os.environ["TRABALHOS_TECNICOS_PROVIDER"] = "local"
|
| 151 |
+
os.environ["TRABALHOS_TECNICOS_DB_LOCAL_PATH"] = str(trabalhos_db)
|
| 152 |
+
|
| 153 |
+
users_file = _expand_path(paths_cfg.get("users_file"), base_dir=config_base_dir)
|
| 154 |
+
if users_file is not None:
|
| 155 |
+
os.environ["APP_USERS_FILE"] = str(users_file)
|
| 156 |
+
|
| 157 |
+
logs_mode = env_cfg.get("APP_LOGS_MODE")
|
| 158 |
+
if logs_mode is None and getattr(sys, "frozen", False):
|
| 159 |
+
logs_mode = "disabled"
|
| 160 |
+
_set_env_if_value("APP_LOGS_MODE", logs_mode)
|
| 161 |
+
|
| 162 |
+
for key, value in env_cfg.items():
|
| 163 |
+
if key == "APP_LOGS_MODE":
|
| 164 |
+
continue
|
| 165 |
+
_set_env_if_value(key, value)
|
| 166 |
+
|
| 167 |
+
settings = RuntimeSettings(
|
| 168 |
+
config_path=config_file if config_file and config_file.exists() else None,
|
| 169 |
+
app_root=app_root,
|
| 170 |
+
runtime_dir=runtime_dir,
|
| 171 |
+
host=str(server_cfg.get("host") or "127.0.0.1").strip() or "127.0.0.1",
|
| 172 |
+
port=_as_int(server_cfg.get("port"), 8000),
|
| 173 |
+
open_browser=_as_bool(server_cfg.get("open_browser"), True),
|
| 174 |
+
)
|
| 175 |
+
_APPLIED_SETTINGS = settings
|
| 176 |
+
return settings
|
backend/app/services/auth_service.py
CHANGED
|
@@ -10,7 +10,10 @@ from typing import Any
|
|
| 10 |
|
| 11 |
from fastapi import HTTPException, Request
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
_USERS_LOCK = Lock()
|
| 16 |
_SESSIONS_LOCK = Lock()
|
|
|
|
| 10 |
|
| 11 |
from fastapi import HTTPException, Request
|
| 12 |
|
| 13 |
+
from app.runtime_config import resolve_core_path
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
DEFAULT_USERS_FILE = resolve_core_path("auth", "usuarios.json")
|
| 17 |
|
| 18 |
_USERS_LOCK = Lock()
|
| 19 |
_SESSIONS_LOCK = Lock()
|
backend/app/services/elaboracao_service.py
CHANGED
|
@@ -54,12 +54,13 @@ from app.core.elaboracao.formatadores import (
|
|
| 54 |
formatar_outliers_anteriores_html,
|
| 55 |
)
|
| 56 |
from app.models.session import SessionState
|
|
|
|
| 57 |
from app.services import model_repository
|
| 58 |
from app.services.equacao_service import build_equacoes_payload, exportar_planilha_equacao
|
| 59 |
from app.services.knn_avaliacao_service import estimar_valor_knn_avaliacao
|
| 60 |
from app.services.serializers import dataframe_to_payload, figure_to_payload, sanitize_value
|
| 61 |
|
| 62 |
-
_AVALIADORES_PATH =
|
| 63 |
_AVALIADORES_CACHE: list[dict[str, Any]] | None = None
|
| 64 |
LIMIAR_DISPERSAO_PNG = 1500
|
| 65 |
LOGGER = logging.getLogger(__name__)
|
|
|
|
| 54 |
formatar_outliers_anteriores_html,
|
| 55 |
)
|
| 56 |
from app.models.session import SessionState
|
| 57 |
+
from app.runtime_config import resolve_core_path
|
| 58 |
from app.services import model_repository
|
| 59 |
from app.services.equacao_service import build_equacoes_payload, exportar_planilha_equacao
|
| 60 |
from app.services.knn_avaliacao_service import estimar_valor_knn_avaliacao
|
| 61 |
from app.services.serializers import dataframe_to_payload, figure_to_payload, sanitize_value
|
| 62 |
|
| 63 |
+
_AVALIADORES_PATH = resolve_core_path("elaboracao", "avaliadores.json")
|
| 64 |
_AVALIADORES_CACHE: list[dict[str, Any]] | None = None
|
| 65 |
LIMIAR_DISPERSAO_PNG = 1500
|
| 66 |
LOGGER = logging.getLogger(__name__)
|
backend/app/services/model_repository.py
CHANGED
|
@@ -10,6 +10,7 @@ from threading import Lock
|
|
| 10 |
from typing import Any
|
| 11 |
|
| 12 |
from fastapi import HTTPException
|
|
|
|
| 13 |
|
| 14 |
try:
|
| 15 |
from huggingface_hub import CommitOperationAdd, CommitOperationDelete, HfApi, snapshot_download
|
|
@@ -20,7 +21,7 @@ except Exception: # pragma: no cover - dependência opcional em tempo de import
|
|
| 20 |
snapshot_download = None # type: ignore[assignment]
|
| 21 |
|
| 22 |
|
| 23 |
-
DEFAULT_LOCAL_MODELOS_DIR =
|
| 24 |
DEFAULT_HF_REPO_ID = "gui-sparim/repositorio_mesa"
|
| 25 |
DEFAULT_HF_REVISION = "main"
|
| 26 |
DEFAULT_HF_SUBDIR = "modelos_dai"
|
|
|
|
| 10 |
from typing import Any
|
| 11 |
|
| 12 |
from fastapi import HTTPException
|
| 13 |
+
from app.runtime_config import resolve_core_path
|
| 14 |
|
| 15 |
try:
|
| 16 |
from huggingface_hub import CommitOperationAdd, CommitOperationDelete, HfApi, snapshot_download
|
|
|
|
| 21 |
snapshot_download = None # type: ignore[assignment]
|
| 22 |
|
| 23 |
|
| 24 |
+
DEFAULT_LOCAL_MODELOS_DIR = resolve_core_path("pesquisa", "modelos_dai")
|
| 25 |
DEFAULT_HF_REPO_ID = "gui-sparim/repositorio_mesa"
|
| 26 |
DEFAULT_HF_REVISION = "main"
|
| 27 |
DEFAULT_HF_SUBDIR = "modelos_dai"
|
backend/app/services/session_store.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
|
|
|
| 3 |
import shutil
|
| 4 |
import tempfile
|
| 5 |
import uuid
|
|
@@ -16,7 +17,12 @@ class SessionStore:
|
|
| 16 |
|
| 17 |
def create(self) -> SessionState:
|
| 18 |
session_id = uuid.uuid4().hex
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
state = SessionState(session_id=session_id, workdir=workdir)
|
| 21 |
self._sessions[session_id] = state
|
| 22 |
return state
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
+
import os
|
| 4 |
import shutil
|
| 5 |
import tempfile
|
| 6 |
import uuid
|
|
|
|
| 17 |
|
| 18 |
def create(self) -> SessionState:
|
| 19 |
session_id = uuid.uuid4().hex
|
| 20 |
+
runtime_root = str(os.getenv("MESA_RUNTIME_DIR") or "").strip()
|
| 21 |
+
temp_root = None
|
| 22 |
+
if runtime_root:
|
| 23 |
+
temp_root = Path(runtime_root).expanduser().resolve() / "sessions"
|
| 24 |
+
temp_root.mkdir(parents=True, exist_ok=True)
|
| 25 |
+
workdir = Path(tempfile.mkdtemp(prefix=f"mesa_{session_id[:8]}_", dir=str(temp_root) if temp_root else None))
|
| 26 |
state = SessionState(session_id=session_id, workdir=workdir)
|
| 27 |
self._sessions[session_id] = state
|
| 28 |
return state
|
backend/app/services/trabalhos_tecnicos_repository.py
CHANGED
|
@@ -5,6 +5,7 @@ from dataclasses import dataclass
|
|
| 5 |
from pathlib import Path
|
| 6 |
|
| 7 |
from fastapi import HTTPException
|
|
|
|
| 8 |
|
| 9 |
try:
|
| 10 |
from huggingface_hub import HfApi, hf_hub_download
|
|
@@ -58,7 +59,7 @@ def _local_db_path() -> Path:
|
|
| 58 |
raw = str(os.getenv("TRABALHOS_TECNICOS_DB_LOCAL_PATH") or "").strip()
|
| 59 |
if raw:
|
| 60 |
return Path(raw).expanduser().resolve()
|
| 61 |
-
return (
|
| 62 |
|
| 63 |
|
| 64 |
def _hf_repo_id() -> str:
|
|
|
|
| 5 |
from pathlib import Path
|
| 6 |
|
| 7 |
from fastapi import HTTPException
|
| 8 |
+
from app.runtime_config import resolve_local_data_path
|
| 9 |
|
| 10 |
try:
|
| 11 |
from huggingface_hub import HfApi, hf_hub_download
|
|
|
|
| 59 |
raw = str(os.getenv("TRABALHOS_TECNICOS_DB_LOCAL_PATH") or "").strip()
|
| 60 |
if raw:
|
| 61 |
return Path(raw).expanduser().resolve()
|
| 62 |
+
return resolve_local_data_path(DEFAULT_LOCAL_DB_FILE).resolve()
|
| 63 |
|
| 64 |
|
| 65 |
def _hf_repo_id() -> str:
|