Guilherme Silberfarb Costa commited on
Commit
edfdca7
·
1 Parent(s): 1d89fc8

Add portable Windows build workflow

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .github/workflows/build-windows-portable.yml +51 -0
  2. .gitignore +7 -0
  3. README.md +48 -0
  4. backend/app/core/elaboracao/app.py +3 -1
  5. backend/app/core/elaboracao/formatadores.py +2 -1
  6. backend/app/core/elaboracao/geocodificacao.py +2 -2
  7. backend/app/core/map_layers.py +2 -2
  8. backend/app/core/pesquisa/modelos_dai/MOD_A_CCOM_Z2_004D.dai +0 -3
  9. backend/app/core/pesquisa/modelos_dai/MOD_A_CCOM_Z2_005.dai +0 -3
  10. backend/app/core/pesquisa/modelos_dai/MOD_A_CCOM_Z4_002N.dai +0 -3
  11. backend/app/core/pesquisa/modelos_dai/MOD_A_DEP_Z1_Z2_Z3_Z4_003B.dai +0 -3
  12. backend/app/core/pesquisa/modelos_dai/MOD_A_EDIF_Z1-Z2_001F.dai +0 -3
  13. backend/app/core/pesquisa/modelos_dai/MOD_A_EDIF_Z1_005D.dai +0 -3
  14. backend/app/core/pesquisa/modelos_dai/MOD_A_EDIF_Z1_Z2_001E.dai +0 -3
  15. backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_003F.dai +0 -3
  16. backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_004C.dai +0 -3
  17. backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_006B.dai +0 -3
  18. backend/app/core/pesquisa/modelos_dai/MOD_A_LOJA_Z1_007C.dai +0 -3
  19. backend/app/core/pesquisa/modelos_dai/MOD_A_SALA_Z1_006B.dai +0 -3
  20. backend/app/core/pesquisa/modelos_dai/MOD_A_SALA_Z1_006C.dai +0 -3
  21. backend/app/core/pesquisa/modelos_dai/MOD_A_SALA_Z1_Z3_001.dai +0 -3
  22. backend/app/core/pesquisa/modelos_dai/MOD_V_AP_Z1_011D.dai +0 -3
  23. backend/app/core/pesquisa/modelos_dai/MOD_V_AP_Z1_020B.dai +0 -3
  24. backend/app/core/pesquisa/modelos_dai/MOD_V_AP_Z1_022.dai +0 -3
  25. backend/app/core/pesquisa/modelos_dai/MOD_V_EDIF_Z1_Z2_Z3_Z4_002E.dai +0 -3
  26. backend/app/core/pesquisa/modelos_dai/MOD_V_RCOND_Z4_004.dai +0 -3
  27. backend/app/core/pesquisa/modelos_dai/MOD_V_SALA_Z1_002E.dai +0 -3
  28. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_GENERICO_2016_2026_001.dai +0 -3
  29. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z1_006L.dai +0 -3
  30. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_008C.dai +0 -3
  31. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_013C.dai +0 -3
  32. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_013F.dai +0 -3
  33. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_015C_PORTO_SECO.dai +0 -3
  34. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_001H.dai +0 -3
  35. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_001i.dai +0 -3
  36. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_002A.dai +0 -3
  37. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z2_Z3_Z4_002B.dai +0 -3
  38. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z4_003J.dai +0 -3
  39. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z4_016E.dai +0 -3
  40. backend/app/core/pesquisa/modelos_dai/MOD_V_TER_Z5_007C.dai +0 -3
  41. backend/app/core/pesquisa/modelos_dai/README.md +5 -0
  42. backend/app/core/visualizacao/app.py +20 -2
  43. backend/app/main.py +4 -1
  44. backend/app/portable_launcher.py +77 -0
  45. backend/app/runtime_config.py +176 -0
  46. backend/app/services/auth_service.py +4 -1
  47. backend/app/services/elaboracao_service.py +2 -1
  48. backend/app/services/model_repository.py +2 -1
  49. backend/app/services/session_store.py +7 -1
  50. 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
- _avaliadores_path = os.path.join(os.path.dirname(__file__), "avaliadores.json")
 
 
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 = os.path.join(os.path.dirname(__file__), "styles.css")
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
- _BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # raiz MESA/
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
- _BASE_DIR = Path(__file__).resolve().parent
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 = os.path.join(os.path.dirname(__file__), "styles.css")
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 = Path(__file__).resolve().parents[2] / "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
- DEFAULT_USERS_FILE = Path(__file__).resolve().parent.parent / "core" / "auth" / "usuarios.json"
 
 
 
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 = Path(__file__).resolve().parent.parent / "core" / "elaboracao" / "avaliadores.json"
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 = Path(__file__).resolve().parent.parent / "core" / "pesquisa" / "modelos_dai"
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
- workdir = Path(tempfile.mkdtemp(prefix=f"mesa_{session_id[:8]}_"))
 
 
 
 
 
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 (Path(__file__).resolve().parents[2] / "local_data" / DEFAULT_LOCAL_DB_FILE).resolve()
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: