Spaces:
Running
Running
Guilherme Silberfarb Costa commited on
Commit ·
a577d9a
1
Parent(s): b0eaf10
alteracoes generalizadas
Browse files- backend/app/api/elaboracao.py +4 -0
- backend/app/api/pesquisa.py +2 -0
- backend/app/core/auth/usuarios.json +4 -4
- backend/app/core/dados/Bairros_LC12112_16.cpg +1 -0
- backend/app/core/dados/Bairros_LC12112_16.dbf +3 -0
- backend/app/core/dados/Bairros_LC12112_16.prj +1 -0
- backend/app/core/dados/Bairros_LC12112_16.sbn +0 -0
- backend/app/core/dados/Bairros_LC12112_16.sbx +0 -0
- backend/app/core/dados/Bairros_LC12112_16.shp +3 -0
- backend/app/core/dados/Bairros_LC12112_16.shp.xml +57 -0
- backend/app/core/dados/Bairros_LC12112_16.shx +3 -0
- backend/app/core/elaboracao/charts.py +3 -1
- backend/app/core/elaboracao/core.py +2 -2
- backend/app/core/map_layers.py +105 -0
- backend/app/core/visualizacao/app.py +2 -0
- backend/app/services/elaboracao_service.py +33 -2
- backend/app/services/pesquisa_service.py +87 -0
- backend/app/services/visualizacao_service.py +8 -1
- frontend/src/api.js +3 -1
- frontend/src/components/ElaboracaoTab.jsx +199 -51
- frontend/src/components/PesquisaTab.jsx +26 -11
- frontend/src/components/SinglePillAutocomplete.jsx +241 -0
- frontend/src/components/VisualizacaoTab.jsx +22 -16
- frontend/src/styles.css +113 -1
backend/app/api/elaboracao.py
CHANGED
|
@@ -63,6 +63,8 @@ class ClassificarXPayload(SessionPayload):
|
|
| 63 |
class SearchTransformPayload(SessionPayload):
|
| 64 |
grau_min_coef: int = 0
|
| 65 |
grau_min_f: int = 0
|
|
|
|
|
|
|
| 66 |
|
| 67 |
|
| 68 |
class AdoptSuggestionPayload(SessionPayload):
|
|
@@ -250,6 +252,8 @@ def search_transformations(payload: SearchTransformPayload) -> dict[str, Any]:
|
|
| 250 |
session,
|
| 251 |
grau_min_coef=payload.grau_min_coef,
|
| 252 |
grau_min_f=payload.grau_min_f,
|
|
|
|
|
|
|
| 253 |
)
|
| 254 |
|
| 255 |
|
|
|
|
| 63 |
class SearchTransformPayload(SessionPayload):
|
| 64 |
grau_min_coef: int = 0
|
| 65 |
grau_min_f: int = 0
|
| 66 |
+
transformacoes_fixas: dict[str, str] = Field(default_factory=dict)
|
| 67 |
+
transformacao_y_fixa: str | None = None
|
| 68 |
|
| 69 |
|
| 70 |
class AdoptSuggestionPayload(SessionPayload):
|
|
|
|
| 252 |
session,
|
| 253 |
grau_min_coef=payload.grau_min_coef,
|
| 254 |
grau_min_f=payload.grau_min_f,
|
| 255 |
+
transformacoes_fixas_usuario=payload.transformacoes_fixas,
|
| 256 |
+
transformacao_y_fixa_usuario=payload.transformacao_y_fixa,
|
| 257 |
)
|
| 258 |
|
| 259 |
|
backend/app/api/pesquisa.py
CHANGED
|
@@ -65,6 +65,7 @@ def pesquisar_modelos(
|
|
| 65 |
rh_max: float | None = Query(None),
|
| 66 |
aval_finalidade: str | None = Query(None),
|
| 67 |
aval_finalidade_colunas: str | None = Query(None),
|
|
|
|
| 68 |
aval_bairro: str | None = Query(None),
|
| 69 |
aval_bairro_colunas: str | None = Query(None),
|
| 70 |
aval_endereco: str | None = Query(None),
|
|
@@ -108,6 +109,7 @@ def pesquisar_modelos(
|
|
| 108 |
rh_max=rh_max,
|
| 109 |
aval_finalidade=aval_finalidade,
|
| 110 |
aval_finalidade_colunas=_split_csv(aval_finalidade_colunas),
|
|
|
|
| 111 |
aval_bairro=aval_bairro,
|
| 112 |
aval_bairro_colunas=_split_csv(aval_bairro_colunas),
|
| 113 |
aval_endereco=aval_endereco,
|
|
|
|
| 65 |
rh_max: float | None = Query(None),
|
| 66 |
aval_finalidade: str | None = Query(None),
|
| 67 |
aval_finalidade_colunas: str | None = Query(None),
|
| 68 |
+
aval_zona: str | None = Query(None),
|
| 69 |
aval_bairro: str | None = Query(None),
|
| 70 |
aval_bairro_colunas: str | None = Query(None),
|
| 71 |
aval_endereco: str | None = Query(None),
|
|
|
|
| 109 |
rh_max=rh_max,
|
| 110 |
aval_finalidade=aval_finalidade,
|
| 111 |
aval_finalidade_colunas=_split_csv(aval_finalidade_colunas),
|
| 112 |
+
aval_zona=aval_zona,
|
| 113 |
aval_bairro=aval_bairro,
|
| 114 |
aval_bairro_colunas=_split_csv(aval_bairro_colunas),
|
| 115 |
aval_endereco=aval_endereco,
|
backend/app/core/auth/usuarios.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
{
|
| 2 |
"usuarios": [
|
| 3 |
{ "usuario": "adriana", "matricula": "188661", "nome": "ADRIANA KIRSCH BISSIGO", "perfil": "viewer" },
|
| 4 |
-
{ "usuario": "carla", "matricula": "458846", "nome": "CARLA ALEXANDRA GODOY FELICE", "perfil": "
|
| 5 |
{ "usuario": "charles", "matricula": "1650645", "nome": "CHARLES ELIS SANDER PINTO DIAS", "perfil": "viewer" },
|
| 6 |
{ "usuario": "clara", "matricula": "1515977", "nome": "CLARA FRANCISCA MARQUES", "perfil": "viewer" },
|
| 7 |
{ "usuario": "daniela", "matricula": "1335618", "nome": "DANIELA CRISTINA JAHNEL", "perfil": "viewer" },
|
|
@@ -9,13 +9,13 @@
|
|
| 9 |
{ "usuario": "debora", "matricula": "1507850", "nome": "DEBORA FONSECA ALVES", "perfil": "viewer" },
|
| 10 |
{ "usuario": "edgar", "matricula": "1507559", "nome": "EDGAR ALEJANDRO VARGAS", "perfil": "viewer" },
|
| 11 |
{ "usuario": "fernanda_lazzari", "matricula": "1298640", "nome": "FERNANDA LAZZARI COSTI", "perfil": "viewer" },
|
| 12 |
-
{ "usuario": "fernanda_pontel", "matricula": "1036130", "nome": "FERNANDA PONTEL", "perfil": "
|
| 13 |
{ "usuario": "fernando", "matricula": "1279670", "nome": "FERNANDO ROBERTO SCHWARTZER", "perfil": "viewer" },
|
| 14 |
{ "usuario": "francisco", "matricula": "1507583", "nome": "FRANCISCO TESTON TISBIEREK", "perfil": "viewer" },
|
| 15 |
{ "usuario": "gilmara", "matricula": "326310", "nome": "GILMARA MULLER", "perfil": "viewer" },
|
| 16 |
{ "usuario": "guilherme", "matricula": "1177028", "nome": "GUILHERME SILBERFARB COSTA", "perfil": "admin" },
|
| 17 |
{ "usuario": "gustavo", "matricula": "1043714", "nome": "GUSTAVO NATANIEL BARCELOS BASTOS", "perfil": "viewer" },
|
| 18 |
-
{ "usuario": "jessica", "matricula": "1279688", "nome": "JESSICA LANGE", "perfil": "
|
| 19 |
{ "usuario": "joao", "matricula": "1226916", "nome": "JOAO MARCIO LOPES MORALLES", "perfil": "viewer" },
|
| 20 |
{ "usuario": "jones", "matricula": "1512170", "nome": "JONES RITTA RODRIGUES", "perfil": "viewer" },
|
| 21 |
{ "usuario": "julio", "matricula": "221287", "nome": "JULIO CESAR PEREIRA DIVAN", "perfil": "viewer" },
|
|
@@ -24,6 +24,6 @@
|
|
| 24 |
{ "usuario": "roberta", "matricula": "370440", "nome": "ROBERTA BRENNER AYUB", "perfil": "viewer" },
|
| 25 |
{ "usuario": "sabrina", "matricula": "1510428", "nome": "SABRINA VILLANOVA IBEIRO", "perfil": "viewer" },
|
| 26 |
{ "usuario": "vanessa_rocha", "matricula": "1052560", "nome": "VANESSA COSTA DA ROCHA", "perfil": "viewer" },
|
| 27 |
-
{ "usuario": "vanessa_staats", "matricula": "1507540", "nome": "VANESSA STAATS", "perfil": "
|
| 28 |
]
|
| 29 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"usuarios": [
|
| 3 |
{ "usuario": "adriana", "matricula": "188661", "nome": "ADRIANA KIRSCH BISSIGO", "perfil": "viewer" },
|
| 4 |
+
{ "usuario": "carla", "matricula": "458846", "nome": "CARLA ALEXANDRA GODOY FELICE", "perfil": "admin" },
|
| 5 |
{ "usuario": "charles", "matricula": "1650645", "nome": "CHARLES ELIS SANDER PINTO DIAS", "perfil": "viewer" },
|
| 6 |
{ "usuario": "clara", "matricula": "1515977", "nome": "CLARA FRANCISCA MARQUES", "perfil": "viewer" },
|
| 7 |
{ "usuario": "daniela", "matricula": "1335618", "nome": "DANIELA CRISTINA JAHNEL", "perfil": "viewer" },
|
|
|
|
| 9 |
{ "usuario": "debora", "matricula": "1507850", "nome": "DEBORA FONSECA ALVES", "perfil": "viewer" },
|
| 10 |
{ "usuario": "edgar", "matricula": "1507559", "nome": "EDGAR ALEJANDRO VARGAS", "perfil": "viewer" },
|
| 11 |
{ "usuario": "fernanda_lazzari", "matricula": "1298640", "nome": "FERNANDA LAZZARI COSTI", "perfil": "viewer" },
|
| 12 |
+
{ "usuario": "fernanda_pontel", "matricula": "1036130", "nome": "FERNANDA PONTEL", "perfil": "admin" },
|
| 13 |
{ "usuario": "fernando", "matricula": "1279670", "nome": "FERNANDO ROBERTO SCHWARTZER", "perfil": "viewer" },
|
| 14 |
{ "usuario": "francisco", "matricula": "1507583", "nome": "FRANCISCO TESTON TISBIEREK", "perfil": "viewer" },
|
| 15 |
{ "usuario": "gilmara", "matricula": "326310", "nome": "GILMARA MULLER", "perfil": "viewer" },
|
| 16 |
{ "usuario": "guilherme", "matricula": "1177028", "nome": "GUILHERME SILBERFARB COSTA", "perfil": "admin" },
|
| 17 |
{ "usuario": "gustavo", "matricula": "1043714", "nome": "GUSTAVO NATANIEL BARCELOS BASTOS", "perfil": "viewer" },
|
| 18 |
+
{ "usuario": "jessica", "matricula": "1279688", "nome": "JESSICA LANGE", "perfil": "admin" },
|
| 19 |
{ "usuario": "joao", "matricula": "1226916", "nome": "JOAO MARCIO LOPES MORALLES", "perfil": "viewer" },
|
| 20 |
{ "usuario": "jones", "matricula": "1512170", "nome": "JONES RITTA RODRIGUES", "perfil": "viewer" },
|
| 21 |
{ "usuario": "julio", "matricula": "221287", "nome": "JULIO CESAR PEREIRA DIVAN", "perfil": "viewer" },
|
|
|
|
| 24 |
{ "usuario": "roberta", "matricula": "370440", "nome": "ROBERTA BRENNER AYUB", "perfil": "viewer" },
|
| 25 |
{ "usuario": "sabrina", "matricula": "1510428", "nome": "SABRINA VILLANOVA IBEIRO", "perfil": "viewer" },
|
| 26 |
{ "usuario": "vanessa_rocha", "matricula": "1052560", "nome": "VANESSA COSTA DA ROCHA", "perfil": "viewer" },
|
| 27 |
+
{ "usuario": "vanessa_staats", "matricula": "1507540", "nome": "VANESSA STAATS", "perfil": "admin" }
|
| 28 |
]
|
| 29 |
}
|
backend/app/core/dados/Bairros_LC12112_16.cpg
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
UTF-8
|
backend/app/core/dados/Bairros_LC12112_16.dbf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:044858d12fd1dc12b82c9012aad1f506e27c2f2406a1330e6bb117c58592e347
|
| 3 |
+
size 15234
|
backend/app/core/dados/Bairros_LC12112_16.prj
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
PROJCS["TM-POA",GEOGCS["GCS_SIRGAS_2000",DATUM["D_SIRGAS_2000",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",300000.0],PARAMETER["False_Northing",5000000.0],PARAMETER["Central_Meridian",-51.0],PARAMETER["Scale_Factor",0.999995],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]]
|
backend/app/core/dados/Bairros_LC12112_16.sbn
ADDED
|
Binary file (1.48 kB). View file
|
|
|
backend/app/core/dados/Bairros_LC12112_16.sbx
ADDED
|
Binary file (244 Bytes). View file
|
|
|
backend/app/core/dados/Bairros_LC12112_16.shp
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1446d8cc7fdff70154b792fd1cf78b251d064ce38a58c8a81128702d2352967a
|
| 3 |
+
size 5562340
|
backend/app/core/dados/Bairros_LC12112_16.shp.xml
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<metadata xml:lang="pt"><Esri><CreaDate>20170123</CreaDate><CreaTime>13572700</CreaTime><ArcGISFormat>1.0</ArcGISFormat><ArcGISstyle>ISO 19139 Metadata Implementation Specification GML3.2</ArcGISstyle><SyncOnce>FALSE</SyncOnce><DataProperties><itemProps><itemName Sync="TRUE">Bairros_LC12112_16</itemName><itemSize Sync="TRUE">5.305</itemSize><itemLocation><linkage Sync="FALSE">withheld</linkage><protocol Sync="TRUE">Local Area Network</protocol></itemLocation><nativeExtBox><westBL Sync="TRUE">271062.949100</westBL><eastBL Sync="TRUE">298900.245600</eastBL><southBL Sync="TRUE">1650035.887400</southBL><northBL Sync="TRUE">1687546.462300</northBL><exTypeCode Sync="TRUE">1</exTypeCode></nativeExtBox><imsContentType Sync="TRUE" export="False">002</imsContentType></itemProps><coordRef><type Sync="TRUE">Projected</type><geogcsn Sync="TRUE">GCS_SIRGAS_2000</geogcsn><csUnits Sync="TRUE">Linear Unit: Meter (1.000000)</csUnits><projcsn Sync="TRUE">TM-POA</projcsn><peXml Sync="TRUE"><ProjectedCoordinateSystem xsi:type='typens:ProjectedCoordinateSystem' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xs='http://www.w3.org/2001/XMLSchema' xmlns:typens='http://www.esri.com/schemas/ArcGIS/10.6'><WKT>PROJCS[&quot;TM-POA&quot;,GEOGCS[&quot;GCS_SIRGAS_2000&quot;,DATUM[&quot;D_SIRGAS_2000&quot;,SPHEROID[&quot;GRS_1980&quot;,6378137.0,298.257222101]],PRIMEM[&quot;Greenwich&quot;,0.0],UNIT[&quot;Degree&quot;,0.0174532925199433]],PROJECTION[&quot;Transverse_Mercator&quot;],PARAMETER[&quot;False_Easting&quot;,300000.0],PARAMETER[&quot;False_Northing&quot;,5000000.0],PARAMETER[&quot;Central_Meridian&quot;,-51.0],PARAMETER[&quot;Scale_Factor&quot;,0.999995],PARAMETER[&quot;Latitude_Of_Origin&quot;,0.0],UNIT[&quot;Meter&quot;,1.0]]</WKT><XOrigin>-5323100</XOrigin><YOrigin>-5002100</YOrigin><XYScale>450265407.00157917</XYScale><ZOrigin>-100000</ZOrigin><ZScale>10000</ZScale><MOrigin>-100000</MOrigin><MScale>10000</MScale><XYTolerance>0.001</XYTolerance><ZTolerance>0.001</ZTolerance><MTolerance>0.001</MTolerance><HighPrecision>true</HighPrecision></ProjectedCoordinateSystem></peXml></coordRef></DataProperties><SyncDate>20180322</SyncDate><SyncTime>15220700</SyncTime><ModDate>20180322</ModDate><ModTime>15220700</ModTime><locales><locale language="por" country="BR"/></locales><scaleRange><minScale>50000</minScale><maxScale>5000</maxScale></scaleRange><ArcGISProfile>NAP</ArcGISProfile></Esri><mdChar><CharSetCd value="004"/></mdChar><dataIdInfo><idCitation><resTitle Sync="FALSE">Limites dos Bairros do Município de Porto Alegre</resTitle><presForm><fgdcGeoform>vector digital data</fgdcGeoform><PresFormCd value="005" Sync="TRUE"/></presForm><date><pubDate>2017-01-30T00:00:00</pubDate><reviseDate>2017-01-30T00:00:00</reviseDate></date><citRespParty><rpIndName>Unidade de Monitoramento do Desenvolvimento Urbano (UMDU)</rpIndName><rpOrgName>Secretaria Municipal de Urbanismo (SMUrb) de Porto Alegre - RS</rpOrgName><role><RoleCd value="003"/></role></citRespParty></idCitation><idPurp>Representação vetorial dos limites de bairros de Porto Alegre.</idPurp><idAbs><DIV STYLE="text-align:Left;"><DIV><DIV><P><SPAN>Representação vetorial dos limites de bairros de Porto Alegre, definidos pela Lei Nº 12.112, de 22 de agosto de 2016. De acordo com o Art. 2º desta lei, o limite individual de cada bairro é representado através de redação descritiva padronizada e espacialização gráfica georreferenciada, sendo considerados os seguintes referenciais:</SPAN></P><P><SPAN>I - eixo central da via (eixo de logradouros), quando se tratar de sistema viário (avenida, rua, estrada, travessa, beco e outros);</SPAN></P><P><SPAN>II - eixo central do leito ou talvegue, quando se tratar do Rio Gravataí e de arroios;</SPAN></P><P><SPAN>III - margem ou orla, quando se tratar do Lago Guaíba;</SPAN></P><P><SPAN>IV - coordenadas georreferenciadas, quando se tratar de torre de alta tensão e sempre que necessária a sua utilização; </SPAN></P><P><SPAN>V - limite de propriedade, preferencialmente de uso institucional, na ausência dos elementos físicos citados nos incs. I a IV;</SPAN></P><P><SPAN>VI - linha reta e imaginária, com uma ou ambas as extremidades definidas por coordenadas georreferenciadas, na ausência das situações dos incs. I a V.</SPAN></P><P><SPAN /></P><P><SPAN STYLE="font-style:italic;">Escala </SPAN><SPAN STYLE="font-style:italic;">do aerolevantamento base</SPAN><SPAN STYLE="font-style:italic;">: 1:1000</SPAN></P><P><SPAN STYLE="font-style:italic;">Observação quanto ao Sistema Geodésico de Referência e à Proje��ão Cartográfica utilizada:</SPAN></P><P><SPAN STYLE="font-style:italic;">O Decreto Municipal n° 18.315/13 estabelece que os trabalhos de delimitação e demarcação de terras, de topografia e cartografia realizadas e/ou contratados por Órgãos da Administração Municipal Direta ou Indireta, bem como os projetos relacionados a essas atividades, que dependam de exame, aprovação ou controle desses Órgãos, devam estar referenciados ao Sistema Geodésico de Referência SIRGAS2000 e suas coordenadas representadas na Projeção Transversa de Mercator para Porto Alegre (TM-POA).</SPAN></P><P><SPAN STYLE="font-style:italic;">Para maiores informações sobre características e parâmetros de transformação, acessar: </SPAN></P><P><A href="http://www2.portoalegre.rs.gov.br/spm/default.php?p_secao=345"><SPAN>http://www2.portoalegre.rs.gov.br/spm/default.php?p_secao=345</SPAN></A></P><P><SPAN /></P></DIV></DIV></DIV></idAbs><idCredit>Unidade de Monitoramento do Desenvolvimento Urbano (UMDU)
|
| 2 |
+
Secretaria Municipal de Urbanismo (SMUrb)
|
| 3 |
+
Porto Alegre/RS - Brasil</idCredit><searchKeys><keyword>Bairros</keyword><keyword>Bairros Vigentes</keyword><keyword>Limites de Bairros</keyword><keyword>Urbanismo</keyword><keyword>Porto Alegre.</keyword></searchKeys><dataChar><CharSetCd value="004"/></dataChar><idPoC><rpIndName>Unidade de Geoprocessamento e Sensoriamento Remoto Aplicado</rpIndName><rpOrgName>Secretaria Municipal de Urbanismo (SMUrb) de Porto Alegre - RS</rpOrgName><role><RoleCd value="010"/></role><displayName>Unidade de Geoprocessamento e Sensoriamento Remoto Aplicado</displayName></idPoC><resMaint><maintFreq><MaintFreqCd value="009"/></maintFreq><maintCont><rpIndName>Unidade de Geoprocessamento e Sensoriamento Remoto Aplicado</rpIndName><rpOrgName>Secretaria Municipal de Urbanismo (SMUrb) de Porto Alegre - RS</rpOrgName><role><RoleCd value="010"/></role><displayName>Unidade de Geoprocessamento e Sensoriamento Remoto Aplicado</displayName></maintCont><maintCont><rpIndName>Unidade de Monitoramento do Desenvolvimento Urbano (UMDU)</rpIndName><rpOrgName>Secretaria Municipal de Urbanismo (SMUrb) de Porto Alegre - RS</rpOrgName><role><RoleCd value="003"/></role><editorSave>False</editorSave><displayName>Unidade de Monitoramento do Desenvolvimento Urbano (UMDU)</displayName></maintCont></resMaint><resConst><SecConsts><class><ClasscationCd value="001"/></class></SecConsts></resConst><resConst><Consts><useLimit><DIV STYLE="text-align:Left;"><DIV><DIV><P><SPAN>Não recomendado o uso para projetos arquitetônicos executivos, ou outros que exijam alta precisão geométrica.</SPAN></P></DIV></DIV></DIV></useLimit></Consts></resConst><envirDesc Sync="TRUE">Microsoft Windows 7 Version 6.1 (Build 7601) Service Pack 1; Esri ArcGIS 10.6.0.8321</envirDesc><dataLang><languageCode value="por" Sync="TRUE"/><countryCode value="BRA" Sync="TRUE"/></dataLang><spatRpType><SpatRepTypCd value="001" Sync="TRUE"/></spatRpType><dataExt><geoEle><GeoBndBox esriExtentType="search"><exTypeCode Sync="TRUE">1</exTypeCode><westBL Sync="TRUE">-51.300724</westBL><eastBL Sync="TRUE">-51.011390</eastBL><northBL Sync="TRUE">-29.930709</northBL><southBL Sync="TRUE">-30.269428</southBL></GeoBndBox></geoEle></dataExt><idPoC><rpIndName>Unidade de Monitoramento do Desenvolvimento Urbano (UMDU)</rpIndName><rpOrgName>Secretaria Municipal de Urbanismo (SMUrb) de Porto Alegre - RS</rpOrgName><role><RoleCd value="003"/></role><displayName>Unidade de Monitoramento do Desenvolvimento Urbano (UMDU)</displayName></idPoC><tpCat><TopicCatCd value="010"/></tpCat><tpCat><TopicCatCd value="015"/></tpCat><tpCat><TopicCatCd value="003"/></tpCat><tpCat><TopicCatCd value="013"/></tpCat><tpCat><TopicCatCd value="016"/></tpCat></dataIdInfo><mdMaint><maintFreq><MaintFreqCd value="009"/></maintFreq><maintCont><rpIndName>Unidade de Geoprocessamento e Sensoriamento Remoto Aplicado</rpIndName><rpOrgName>Secretaria Municipal de Urbanismo (SMUrb) de Porto Alegre - RS</rpOrgName><role><RoleCd value="003"/></role><displayName>Unidade de Geoprocessamento e Sensoriamento Remoto Aplicado</displayName></maintCont></mdMaint><mdConst><SecConsts><class><ClasscationCd value="001"/></class></SecConsts></mdConst><dataSetFn><OnFunctCd value="001"/></dataSetFn><eainfo><detailed Name="Bairros_LC12112_16"><enttyp><enttypl Sync="TRUE">Bairros_LC12112_16</enttypl><enttypt Sync="TRUE">Feature Class</enttypt><enttypc Sync="TRUE">128</enttypc></enttyp><attr><attrlabl Sync="TRUE">FID</attrlabl><attalias Sync="TRUE">FID</attalias><attrtype Sync="TRUE">OID</attrtype><attwidth Sync="TRUE">4</attwidth><atprecis Sync="TRUE">0</atprecis><attscale Sync="TRUE">0</attscale><attrdef Sync="TRUE">Internal feature number.</attrdef><attrdefs Sync="TRUE">Esri</attrdefs><attrdomv><udom Sync="TRUE">Sequential unique whole numbers that are automatically generated.</udom></attrdomv></attr><attr><attrlabl Sync="TRUE">Shape</attrlabl><attalias Sync="TRUE">Shape</attalias><attrtype Sync="TRUE">Geometry</attrtype><attwidth Sync="TRUE">0</attwidth><atprecis Sync="TRUE">0</atprecis><attscale Sync="TRUE">0</attscale><attrdef Sync="TRUE">Feature geometry.</attrdef><attrdefs Sync="TRUE">Esri</attrdefs><attrdomv><udom Sync="TRUE">Coordinates defining the features.</udom></attrdomv></attr><attr><attrlabl Sync="TRUE">OBJECTID</attrlabl><attalias Sync="TRUE">OBJECTID</attalias><attrtype Sync="TRUE">Integer</attrtype><attwidth Sync="TRUE">10</attwidth><atprecis Sync="TRUE">10</atprecis><attscale Sync="TRUE">0</attscale></attr><attr><attrlabl Sync="TRUE">CODIGO</attrlabl><attalias Sync="TRUE">CODIGO</attalias><attrtype Sync="TRUE">Integer</attrtype><attwidth Sync="TRUE">10</attwidth><atprecis Sync="TRUE">10</atprecis><attscale Sync="TRUE">0</attscale></attr><attr><attrlabl Sync="TRUE">NOME</attrlabl><attalias Sync="TRUE">NOME</attalias><attrtype Sync="TRUE">String</attrtype><attwidth Sync="TRUE">20</attwidth><atprecis Sync="TRUE">0</atprecis><attscale Sync="TRUE">0</attscale></attr><attr><attrlabl Sync="TRUE">EDITOR</attrlabl><attalias Sync="TRUE">EDITOR</attalias><attrtype Sync="TRUE">String</attrtype><attwidth Sync="TRUE">30</attwidth><atprecis Sync="TRUE">0</atprecis><attscale Sync="TRUE">0</attscale></attr><attr><attrlabl Sync="TRUE">DATA_EDICA</attrlabl><attalias Sync="TRUE">DATA_EDICA</attalias><attrtype Sync="TRUE">Date</attrtype><attwidth Sync="TRUE">8</attwidth><atprecis Sync="TRUE">0</atprecis><attscale Sync="TRUE">0</attscale></attr><attr><attrlabl Sync="TRUE">GEOM_AREA</attrlabl><attalias Sync="TRUE">GEOM_AREA</attalias><attrtype Sync="TRUE">Double</attrtype><attwidth Sync="TRUE">19</attwidth><atprecis Sync="TRUE">0</atprecis><attscale Sync="TRUE">0</attscale></attr><attr><attrlabl Sync="TRUE">GEOM_LEN</attrlabl><attalias Sync="TRUE">GEOM_LEN</attalias><attrtype Sync="TRUE">Double</attrtype><attwidth Sync="TRUE">19</attwidth><atprecis Sync="TRUE">0</atprecis><attscale Sync="TRUE">0</attscale></attr></detailed></eainfo><mdLang><languageCode value="por" Sync="TRUE"/><countryCode value="BRA" Sync="TRUE"/></mdLang><distInfo><distFormat><formatName Sync="TRUE">Shapefile</formatName></distFormat><distTranOps><transSize Sync="TRUE">5.305</transSize></distTranOps></distInfo><mdHrLv><ScopeCd value="005" Sync="TRUE"/></mdHrLv><mdHrLvName Sync="TRUE">dataset</mdHrLvName><refSysInfo><RefSystem><refSysID><identCode code="0" Sync="TRUE"/></refSysID></RefSystem></refSysInfo><spatRepInfo><VectSpatRep><geometObjs Name="Bairros_LC12112_16"><geoObjTyp><GeoObjTypCd value="002" Sync="TRUE"></GeoObjTypCd></geoObjTyp><geoObjCnt Sync="TRUE">128</geoObjCnt></geometObjs><topLvl><TopoLevCd value="001" Sync="TRUE"></TopoLevCd></topLvl></VectSpatRep></spatRepInfo><spdoinfo><ptvctinf><esriterm Name="Bairros_LC12112_16"><efeatyp Sync="TRUE">Simple</efeatyp><efeageom code="4" Sync="TRUE"></efeageom><esritopo Sync="TRUE">FALSE</esritopo><efeacnt Sync="TRUE">128</efeacnt><spindex Sync="TRUE">TRUE</spindex><linrefer Sync="TRUE">FALSE</linrefer></esriterm></ptvctinf></spdoinfo><mdDateSt Sync="TRUE">20180322</mdDateSt><mdFileID>BAIRROS_VIG</mdFileID><Binary><Thumbnail><Data EsriPropertyType="PictureX">/9j/4AAQSkZJRgABAQEAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0a
|
| 4 |
+
HBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIy
|
| 5 |
+
MjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCACFAMgDASIA
|
| 6 |
+
AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA
|
| 7 |
+
AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3
|
| 8 |
+
ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm
|
| 9 |
+
p6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA
|
| 10 |
+
AwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx
|
| 11 |
+
BhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK
|
| 12 |
+
U1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3
|
| 13 |
+
uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3+iii
|
| 14 |
+
gAooooAKKKKACimSyLDC8rZKopY4GTgVn3zm6iEDs9tGzDcxkCswHYYP5/1qJ1IwV5MaVy4l5A6b
|
| 15 |
+
yxjGSP3qGMn8GANTAhgCCCDyCK54XVhbtvtdRjkkLHKeYJN/fBxkjr17e9X9OuoZ3WW2P7mbcGX+
|
| 16 |
+
6468dj1z9BXPTxXNLlta43E06KKK6yQooqGS6giKB5VXfnHPHBwf1IH40ATUUgIIyCCPaloAKKKK
|
| 17 |
+
ACiiigAooooAKKKKACiiigAooooAKKKKACiiigBGUOpVgCpGCD3FZkthBBeCdkEglIQmQ7ihxxgn
|
| 18 |
+
oD/M+9alNkjWWNo3GVYYIrOrTVSLixp2Mu5ubcK8EtrNKF/gFuzq3pg4xWZZqpa7NvIkLFwrmLI8
|
| 19 |
+
tum7aeeqrkHHGa0Hjm2SrJcyZibbtUY+XPDHHzE454IqlfXH2K0uhEzzEw5dpI8YySMnp03DjBJ9
|
| 20 |
+
+TXnUYOLdt1/mU2aAu7qGeNJH86WV3Qx7QqpjJDeuMYHfqKub7ntJF/37P8A8VWTbyAXxM6SF1AL
|
| 21 |
+
SkbgSFwMkcdGPOBn04rRW7tnGVuIiPUOKK2IqRdoPT/MaSKz22pTMfNvoHjzkRG2+X6H5sn/AOsK
|
| 22 |
+
VLW5jyUks1JAB22xGQOn8dTrdwuxCMz46lELD8wMUx3e5k8mMSRx4zJIVKk+y+/qe31ORzyq1Jv3
|
| 23 |
+
mOyK9nOkihY5pYDIThVj2hsZ6ErgnA5x/wDXrUs932VQ7FmUlSx74JFQNbQtEkezakf3AhK7eMcY
|
| 24 |
+
6ccVLp6lLCGMtuaNfLLepXgn9K7cHUcpPX5EyRZooor0CAooooAKKKKACiiigAooooAKKKKACiii
|
| 25 |
+
gAooooAKKKbJvETmMAvtO0HpntQBh6m23UPOkeRYV/dHymKk/Lu7Hk8n6YNV3sootLbZAEikdBIZ
|
| 26 |
+
8pkL8wJB+bO49upGQOcG2vmLdpZzSgzF0lUIMH7xZievHbPAPIq1EVe5lWRt1xGx+91VSTtx+Hp+
|
| 27 |
+
NcMpunzVGutv6/Iq19CPTbf7PZqCNpbkKRjaOwx2+nuadDBDvljaJWKuWBZQSQx3fzJH4Vaqrckx
|
| 28 |
+
TQSpkuWEZQD7wJGfyxn6Z9a81ycm33NC1RSEhVLE4AGSagFw8qgwwswPIZ/kH68/pUWAlm8wwSCI
|
| 29 |
+
gSbTsJ9ccVLaGM2yeUrKoyu1uoIODn1Oc81VN0sThLgLCTnaxYbW+h9f/r1JpU0M9grwyJIoZgXR
|
| 30 |
+
gQxDHJ49ev416OAum0RMu0UUV6RAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFV4JLh7i4SW
|
| 31 |
+
ELEjARSA/fGOeO2OnvViigCpZ2P2ZnmmlM9zJw0rKBwOgAHQVXa4kkvpVhiVgoCsWfABBPseeent
|
| 32 |
+
7ir11I0VrK6Ebwp257t2H51Wt7dLaMomeWLMx6sxOST+NcONqKMOTuVFCW8ryhxIipJG21grbh0B
|
| 33 |
+
4OB2I7VA06LfSSOH2xqEQhC3J5bGM/7P5UXDWaXBklkYMVAYKTggZxnH+8eD/SoLe4KEIu5TklIy
|
| 34 |
+
FVJBzjbx3615qXUssXEyyxiEK37xgh3oVBGfmGT7Zqe4Zkt5GU4IUnOM49x71Va333kbXBUNIDhV
|
| 35 |
+
A44Hy5PJ43enHbipRZlFCx3MyKP4QFI/IjgfTFGisMt/YLXGGgSQ9C0g3E/iasdKqaarJaBXbLKx
|
| 36 |
+
BAzhcdhkk4/xq3XvQacU0YhRRRVAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQBz2pFH
|
| 37 |
+
vvIuWm3u+I2V3CqMFhhRgE/Lx1557Yq3dQzXgHltJCFGVO7GWyCpIHbjofXpV6W3dpjLHIFYqFIZ
|
| 38 |
+
dw4J9x61DFNv2q6lJduSjdR649R7ivKxcJxnzdDSNjHWSETppl2FKoQUaON48nIXBGT69c4ORWz5
|
| 39 |
+
EX7rEajyv9XgY28Y4/CkuYRcW7xnqRlTnGCOh/A02zaV7ZTNzJkgnjnk88VySd1coWQ5uolAJwGJ
|
| 40 |
+
9APX69vxNTVFLCshDFnBCkfIcHBx/gKbbXMc6BRJGZVA8xFblT3yO1T0AkgkEDmOTA8xyUbP3ie3
|
| 41 |
+
1/wq5VC5/wBUDnAEiEn0AYZP5d6v17GDqOdPXpoZyVmFFFFdRIUUUUAFFFFABRRRQAUUUUAFFFFA
|
| 42 |
+
BRRRQAUUUUAFFFFABVW+UeVG+PnSVNp7jJAP6E1arP1BBKbd4pmWUPhdmCMZG7IPsCM9s+9Z1f4c
|
| 43 |
+
vQa3GXVygDRAvuyFOEY8dTg+uPxoW685SLRBIFJXe3yoCDjGe/4UfYh5nmCaTfv3k8cnbt9MdKrG
|
| 44 |
+
2Yapl9+2RcExlkJwOCSuPcfiOleGlE1LYjumPzzxquOfLjwc/Ukj9KhmVUlt7eE7WB3buWIznr65
|
| 45 |
+
5zz/AIiX7KIphLbqikgh1Hyhs45OByRj9TSpBKtz5xlTkbXVUxux07+9K4A1vJK6edIjxryU8vhu
|
| 46 |
+
O/P+f5WLNv3RjY/OpOV9AScY9sf5HSioXlW3uklfzNnlsDtQsM5XGcA47104Sry1LPZkyWhfopqO
|
| 47 |
+
siB0YMp5BU5Bp1ewZhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAIyh0ZW6MMHnFU4tN
|
| 48 |
+
jh+5LIvToqDgduFq7RUyhGXxK4FR7aeNd0c5kI6rIBz9CMYoRxJGrrnDAEZqe4hFxA8RZlDDGVOD
|
| 49 |
+
VRkktpYgZTIsjFdu0DZwTxjtxjnPUc1wYvDK3NBWtuXGXcmooorzCwooooAi+zrk/PJsJyYw2Fz3
|
| 50 |
+
/wD1dPalLtboXEvyKMkSHIx9ev8AOllk8tRwWduEUdWOM4/Sljt3klElwEwnKIrEjPqenTt6dfp2
|
| 51 |
+
YdVptOL0JdkWY2LxKzLtJAJU9qdRRXsGYUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAB
|
| 52 |
+
VBbS7D+dJcJLKMqAVwoUnPGOnb16D61foPTgZqZQU1aQFEtLDLHHMY235CunGSOfunOOO+TSyTxw
|
| 53 |
+
4DuAW+6vUt9B1P4Uz/TJpUne1VAqsuzzQW5I9sdAO/rT7AiR7iYoyyFwuHXBCgDA9xkk/jXnSwil
|
| 54 |
+
VslaJfNoQrfpLdG2hjkkkChj8uAuSR8xPQ8dOtTNI8f+thdFzjeMFf05A9yBUj2hfUYrsOF2IUIC
|
| 55 |
+
8sD2J9M4P4e9WGVXUqyhlIwQRkEVt9Rp2sLmZVt133UsjH7mEUY6ZAJP48D8Kt1UtLSS2ldnnEil
|
| 56 |
+
VRfkwQATjJyc9at10UYckFFibuwooorUQUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAB
|
| 57 |
+
RRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAf/2Q==</Data></Thumbnail></Binary><mdContact><rpIndName>Unidade de Monitoramento do Desenvolvimento Urbano (UMDU)</rpIndName><rpOrgName>Secretaria Municipal de Urbanismo (SMUrb) de Porto Alegre - RS</rpOrgName><role><RoleCd value="003"/></role><displayName>Unidade de Monitoramento do Desenvolvimento Urbano (UMDU)</displayName></mdContact><mdContact><rpIndName>Unidade de Geoprocessamento e Sensoriamento Remoto Aplicado</rpIndName><rpOrgName>Secretaria Municipal de Urbanismo (SMUrb) de Porto Alegre - RS</rpOrgName><role><RoleCd value="010"/></role><displayName>Unidade de Geoprocessamento e Sensoriamento Remoto Aplicado</displayName><displayName>Unidade de Geoprocessamento e Sensoriamento Remoto Aplicado</displayName></mdContact></metadata>
|
backend/app/core/dados/Bairros_LC12112_16.shx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d1f2aae715d854e1cb48e58d9561ab39985f9990aaeec8403cd95074654a320d
|
| 3 |
+
size 1124
|
backend/app/core/elaboracao/charts.py
CHANGED
|
@@ -15,6 +15,7 @@ from folium import plugins
|
|
| 15 |
import branca.colormap as cm
|
| 16 |
from branca.element import Element
|
| 17 |
from html import escape
|
|
|
|
| 18 |
|
| 19 |
# ============================================================
|
| 20 |
# CONSTANTES DE ESTILO
|
|
@@ -815,7 +816,7 @@ def _adicionar_superficie_continua(
|
|
| 815 |
fill=True,
|
| 816 |
fill_color=cor,
|
| 817 |
fill_opacity=0.6,
|
| 818 |
-
tooltip=folium.Tooltip(f"
|
| 819 |
).add_to(camada_superficie)
|
| 820 |
|
| 821 |
camada_superficie.add_to(m)
|
|
@@ -910,6 +911,7 @@ def criar_mapa(
|
|
| 910 |
# Camadas base
|
| 911 |
folium.TileLayer(tiles="OpenStreetMap", name="OpenStreetMap", control=True, show=True).add_to(m)
|
| 912 |
folium.TileLayer(tiles="CartoDB positron", name="Positron", control=True, show=False).add_to(m)
|
|
|
|
| 913 |
|
| 914 |
modo_normalizado = str(modo or "pontos").strip().lower()
|
| 915 |
modo_calor = (
|
|
|
|
| 15 |
import branca.colormap as cm
|
| 16 |
from branca.element import Element
|
| 17 |
from html import escape
|
| 18 |
+
from app.core.map_layers import add_bairros_layer
|
| 19 |
|
| 20 |
# ============================================================
|
| 21 |
# CONSTANTES DE ESTILO
|
|
|
|
| 816 |
fill=True,
|
| 817 |
fill_color=cor,
|
| 818 |
fill_opacity=0.6,
|
| 819 |
+
tooltip=folium.Tooltip(f"{valor_col} interpolado: {valor_fmt}", sticky=False),
|
| 820 |
).add_to(camada_superficie)
|
| 821 |
|
| 822 |
camada_superficie.add_to(m)
|
|
|
|
| 911 |
# Camadas base
|
| 912 |
folium.TileLayer(tiles="OpenStreetMap", name="OpenStreetMap", control=True, show=True).add_to(m)
|
| 913 |
folium.TileLayer(tiles="CartoDB positron", name="Positron", control=True, show=False).add_to(m)
|
| 914 |
+
add_bairros_layer(m, show=True)
|
| 915 |
|
| 916 |
modo_normalizado = str(modo or "pontos").strip().lower()
|
| 917 |
modo_calor = (
|
backend/app/core/elaboracao/core.py
CHANGED
|
@@ -2169,11 +2169,11 @@ def avaliar_imovel(modelo_sm, valores_x, colunas_x, transformacoes_x, transforma
|
|
| 2169 |
min_val = float(limites.loc[col, "Mínimo"])
|
| 2170 |
max_val = float(limites.loc[col, "Máximo"])
|
| 2171 |
|
| 2172 |
-
# Dicotômica, categórica codificada ou percentual — validação já feita no callback
|
| 2173 |
if col in (dicotomicas or []):
|
| 2174 |
extrapolacoes[col] = {"status": "dicotomica", "percentual": 0.0, "direcao": "ok"}
|
| 2175 |
continue
|
| 2176 |
-
if col in (codigo_alocado or []):
|
| 2177 |
extrapolacoes[col] = {"status": "codigo_alocado", "percentual": 0.0, "direcao": "ok"}
|
| 2178 |
continue
|
| 2179 |
if col in (percentuais or []):
|
|
|
|
| 2169 |
min_val = float(limites.loc[col, "Mínimo"])
|
| 2170 |
max_val = float(limites.loc[col, "Máximo"])
|
| 2171 |
|
| 2172 |
+
# Dicotômica, categórica codificada (exceto RH) ou percentual — validação já feita no callback
|
| 2173 |
if col in (dicotomicas or []):
|
| 2174 |
extrapolacoes[col] = {"status": "dicotomica", "percentual": 0.0, "direcao": "ok"}
|
| 2175 |
continue
|
| 2176 |
+
if col in (codigo_alocado or []) and str(col).strip().upper() != "RH":
|
| 2177 |
extrapolacoes[col] = {"status": "codigo_alocado", "percentual": 0.0, "direcao": "ok"}
|
| 2178 |
continue
|
| 2179 |
if col in (percentuais or []):
|
backend/app/core/map_layers.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from threading import Lock
|
| 6 |
+
from typing import Any
|
| 7 |
+
|
| 8 |
+
import folium
|
| 9 |
+
|
| 10 |
+
_BASE_DIR = Path(__file__).resolve().parent
|
| 11 |
+
_BAIRROS_SHP_PATH = _BASE_DIR / "dados" / "Bairros_LC12112_16.shp"
|
| 12 |
+
_TOOLTIP_FIELDS = ("NOME", "BAIRRO", "NME_BAI", "NOME_BAIRRO")
|
| 13 |
+
_SIMPLIFY_TOLERANCE = 0.00005
|
| 14 |
+
|
| 15 |
+
_BAIRROS_CACHE_LOCK = Lock()
|
| 16 |
+
_BAIRROS_GEOJSON_CACHE: dict[str, Any] | None = None
|
| 17 |
+
_BAIRROS_GEOJSON_CARREGADO = False
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _carregar_bairros_geojson() -> dict[str, Any] | None:
|
| 21 |
+
global _BAIRROS_GEOJSON_CACHE, _BAIRROS_GEOJSON_CARREGADO
|
| 22 |
+
with _BAIRROS_CACHE_LOCK:
|
| 23 |
+
if _BAIRROS_GEOJSON_CARREGADO:
|
| 24 |
+
return _BAIRROS_GEOJSON_CACHE
|
| 25 |
+
_BAIRROS_GEOJSON_CARREGADO = True
|
| 26 |
+
|
| 27 |
+
if not _BAIRROS_SHP_PATH.exists():
|
| 28 |
+
return None
|
| 29 |
+
|
| 30 |
+
try:
|
| 31 |
+
import geopandas as gpd
|
| 32 |
+
except Exception:
|
| 33 |
+
return None
|
| 34 |
+
|
| 35 |
+
try:
|
| 36 |
+
gdf = gpd.read_file(_BAIRROS_SHP_PATH, engine="fiona")
|
| 37 |
+
if gdf is None or gdf.empty:
|
| 38 |
+
geojson = None
|
| 39 |
+
else:
|
| 40 |
+
if gdf.crs is not None:
|
| 41 |
+
gdf = gdf.to_crs("EPSG:4326")
|
| 42 |
+
campos = ["geometry"]
|
| 43 |
+
for campo in _TOOLTIP_FIELDS:
|
| 44 |
+
if campo in gdf.columns:
|
| 45 |
+
campos.insert(0, campo)
|
| 46 |
+
break
|
| 47 |
+
gdf = gdf.loc[:, campos].copy()
|
| 48 |
+
if _SIMPLIFY_TOLERANCE > 0:
|
| 49 |
+
gdf["geometry"] = gdf.geometry.simplify(_SIMPLIFY_TOLERANCE, preserve_topology=True)
|
| 50 |
+
geojson = json.loads(gdf.to_json(drop_id=True))
|
| 51 |
+
except Exception:
|
| 52 |
+
geojson = None
|
| 53 |
+
|
| 54 |
+
with _BAIRROS_CACHE_LOCK:
|
| 55 |
+
_BAIRROS_GEOJSON_CACHE = geojson
|
| 56 |
+
return geojson
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def add_bairros_layer(
|
| 60 |
+
mapa: folium.Map,
|
| 61 |
+
*,
|
| 62 |
+
show: bool = True,
|
| 63 |
+
layer_name: str = "Bairros",
|
| 64 |
+
) -> bool:
|
| 65 |
+
geojson = _carregar_bairros_geojson()
|
| 66 |
+
if not geojson:
|
| 67 |
+
return False
|
| 68 |
+
|
| 69 |
+
tooltip = None
|
| 70 |
+
features = geojson.get("features") if isinstance(geojson, dict) else None
|
| 71 |
+
if isinstance(features, list) and features:
|
| 72 |
+
props = features[0].get("properties") if isinstance(features[0], dict) else None
|
| 73 |
+
if isinstance(props, dict):
|
| 74 |
+
for candidate in _TOOLTIP_FIELDS:
|
| 75 |
+
if candidate in props:
|
| 76 |
+
tooltip = folium.GeoJsonTooltip(
|
| 77 |
+
fields=[candidate],
|
| 78 |
+
aliases=["Bairro:"],
|
| 79 |
+
localize=False,
|
| 80 |
+
sticky=False,
|
| 81 |
+
labels=True,
|
| 82 |
+
)
|
| 83 |
+
break
|
| 84 |
+
|
| 85 |
+
folium.GeoJson(
|
| 86 |
+
data=geojson,
|
| 87 |
+
name=layer_name,
|
| 88 |
+
show=show,
|
| 89 |
+
control=True,
|
| 90 |
+
overlay=True,
|
| 91 |
+
smooth_factor=0.6,
|
| 92 |
+
style_function=lambda _: {
|
| 93 |
+
"color": "#4c6882",
|
| 94 |
+
"weight": 1.0,
|
| 95 |
+
"fillColor": "#f39c12",
|
| 96 |
+
"fillOpacity": 0.04,
|
| 97 |
+
},
|
| 98 |
+
highlight_function=lambda _: {
|
| 99 |
+
"color": "#e67e22",
|
| 100 |
+
"weight": 1.6,
|
| 101 |
+
"fillOpacity": 0.12,
|
| 102 |
+
},
|
| 103 |
+
tooltip=tooltip,
|
| 104 |
+
).add_to(mapa)
|
| 105 |
+
return True
|
backend/app/core/visualizacao/app.py
CHANGED
|
@@ -22,6 +22,7 @@ import math
|
|
| 22 |
|
| 23 |
from app.core.elaboracao.core import avaliar_imovel, _migrar_pacote_v1_para_v2, exportar_avaliacoes_excel
|
| 24 |
from app.core.elaboracao.formatadores import formatar_avaliacao_html
|
|
|
|
| 25 |
|
| 26 |
# ============================================================
|
| 27 |
# CONSTANTES
|
|
@@ -741,6 +742,7 @@ def criar_mapa(df, lat_col="lat", lon_col="lon", cor_col=None, tamanho_col=None,
|
|
| 741 |
# Camadas base
|
| 742 |
folium.TileLayer(tiles="OpenStreetMap", name="OpenStreetMap", control=True, show=True).add_to(m)
|
| 743 |
folium.TileLayer(tiles="CartoDB positron", name="Positron", control=True, show=False).add_to(m)
|
|
|
|
| 744 |
|
| 745 |
# Se tamanho_col fornecido mas cor_col não, usa mesma variável para cor
|
| 746 |
if tamanho_col and tamanho_col != "Visualização Padrão" and not cor_col:
|
|
|
|
| 22 |
|
| 23 |
from app.core.elaboracao.core import avaliar_imovel, _migrar_pacote_v1_para_v2, exportar_avaliacoes_excel
|
| 24 |
from app.core.elaboracao.formatadores import formatar_avaliacao_html
|
| 25 |
+
from app.core.map_layers import add_bairros_layer
|
| 26 |
|
| 27 |
# ============================================================
|
| 28 |
# CONSTANTES
|
|
|
|
| 742 |
# Camadas base
|
| 743 |
folium.TileLayer(tiles="OpenStreetMap", name="OpenStreetMap", control=True, show=True).add_to(m)
|
| 744 |
folium.TileLayer(tiles="CartoDB positron", name="Positron", control=True, show=False).add_to(m)
|
| 745 |
+
add_bairros_layer(m, show=True)
|
| 746 |
|
| 747 |
# Se tamanho_col fornecido mas cor_col não, usa mesma variável para cor
|
| 748 |
if tamanho_col and tamanho_col != "Visualização Padrão" and not cor_col:
|
backend/app/services/elaboracao_service.py
CHANGED
|
@@ -50,6 +50,10 @@ _AVALIADORES_PATH = Path(__file__).resolve().parent.parent / "core" / "elaboraca
|
|
| 50 |
_AVALIADORES_CACHE: list[dict[str, Any]] | None = None
|
| 51 |
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
def _parse_data_iso_segura(value: Any) -> str | None:
|
| 54 |
if value is None:
|
| 55 |
return None
|
|
@@ -846,20 +850,44 @@ def apply_selection(
|
|
| 846 |
}
|
| 847 |
|
| 848 |
|
| 849 |
-
def search_transformacoes(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 850 |
df = session.df_filtrado if session.df_filtrado is not None else session.df_original
|
| 851 |
if df is None or not session.coluna_y or not session.colunas_x:
|
| 852 |
raise HTTPException(status_code=400, detail="Selecione variaveis antes de buscar transformacoes")
|
| 853 |
|
| 854 |
transformacoes_fixas: dict[str, str] = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 855 |
for col in (session.dicotomicas or []) + (session.percentuais or []):
|
| 856 |
transformacoes_fixas[col] = "(x)"
|
| 857 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 858 |
resultados = buscar_melhores_transformacoes(
|
| 859 |
df,
|
| 860 |
session.coluna_y,
|
| 861 |
session.colunas_x,
|
| 862 |
transformacoes_fixas=transformacoes_fixas,
|
|
|
|
| 863 |
top_n=5,
|
| 864 |
grau_min_coef=int(grau_min_coef),
|
| 865 |
grau_min_f=int(grau_min_f),
|
|
@@ -1549,7 +1577,10 @@ def calcular_avaliacao_elaboracao(
|
|
| 1549 |
if col in session.codigo_alocado and col in est_idx.index:
|
| 1550 |
min_v = float(est_idx.loc[col, "Mínimo"])
|
| 1551 |
max_v = float(est_idx.loc[col, "Máximo"])
|
| 1552 |
-
if
|
|
|
|
|
|
|
|
|
|
| 1553 |
raise HTTPException(status_code=400, detail=f"{col} aceita inteiros de {int(min_v)} a {int(max_v)}")
|
| 1554 |
if col in session.percentuais and (valor < 0 or valor > 1):
|
| 1555 |
raise HTTPException(status_code=400, detail=f"{col} aceita valores entre 0 e 1")
|
|
|
|
| 50 |
_AVALIADORES_CACHE: list[dict[str, Any]] | None = None
|
| 51 |
|
| 52 |
|
| 53 |
+
def _is_rh_col(coluna: str) -> bool:
|
| 54 |
+
return str(coluna or "").strip().upper() == "RH"
|
| 55 |
+
|
| 56 |
+
|
| 57 |
def _parse_data_iso_segura(value: Any) -> str | None:
|
| 58 |
if value is None:
|
| 59 |
return None
|
|
|
|
| 850 |
}
|
| 851 |
|
| 852 |
|
| 853 |
+
def search_transformacoes(
|
| 854 |
+
session: SessionState,
|
| 855 |
+
grau_min_coef: int = 0,
|
| 856 |
+
grau_min_f: int = 0,
|
| 857 |
+
transformacoes_fixas_usuario: dict[str, str] | None = None,
|
| 858 |
+
transformacao_y_fixa_usuario: str | None = None,
|
| 859 |
+
) -> dict[str, Any]:
|
| 860 |
df = session.df_filtrado if session.df_filtrado is not None else session.df_original
|
| 861 |
if df is None or not session.coluna_y or not session.colunas_x:
|
| 862 |
raise HTTPException(status_code=400, detail="Selecione variaveis antes de buscar transformacoes")
|
| 863 |
|
| 864 |
transformacoes_fixas: dict[str, str] = {}
|
| 865 |
+
for coluna, transformacao in (transformacoes_fixas_usuario or {}).items():
|
| 866 |
+
col = str(coluna or "").strip()
|
| 867 |
+
transf = str(transformacao or "").strip()
|
| 868 |
+
if not col or col not in session.colunas_x:
|
| 869 |
+
continue
|
| 870 |
+
if not transf or transf.lower() == "livre":
|
| 871 |
+
continue
|
| 872 |
+
if transf not in TRANSFORMACOES:
|
| 873 |
+
continue
|
| 874 |
+
transformacoes_fixas[col] = transf
|
| 875 |
+
|
| 876 |
+
# Dicotomicas e percentuais permanecem fixas em (x), independente da selecao manual.
|
| 877 |
for col in (session.dicotomicas or []) + (session.percentuais or []):
|
| 878 |
transformacoes_fixas[col] = "(x)"
|
| 879 |
|
| 880 |
+
transformacao_y_fixa: str | None = None
|
| 881 |
+
transformacao_y_lida = str(transformacao_y_fixa_usuario or "").strip()
|
| 882 |
+
if transformacao_y_lida and transformacao_y_lida.lower() != "livre" and transformacao_y_lida in TRANSFORMACOES:
|
| 883 |
+
transformacao_y_fixa = transformacao_y_lida
|
| 884 |
+
|
| 885 |
resultados = buscar_melhores_transformacoes(
|
| 886 |
df,
|
| 887 |
session.coluna_y,
|
| 888 |
session.colunas_x,
|
| 889 |
transformacoes_fixas=transformacoes_fixas,
|
| 890 |
+
transformacao_y_fixa=transformacao_y_fixa,
|
| 891 |
top_n=5,
|
| 892 |
grau_min_coef=int(grau_min_coef),
|
| 893 |
grau_min_f=int(grau_min_f),
|
|
|
|
| 1577 |
if col in session.codigo_alocado and col in est_idx.index:
|
| 1578 |
min_v = float(est_idx.loc[col, "Mínimo"])
|
| 1579 |
max_v = float(est_idx.loc[col, "Máximo"])
|
| 1580 |
+
if _is_rh_col(col):
|
| 1581 |
+
if float(valor) != int(float(valor)):
|
| 1582 |
+
raise HTTPException(status_code=400, detail=f"{col} aceita apenas valores inteiros")
|
| 1583 |
+
elif float(valor) != int(float(valor)) or valor < min_v or valor > max_v:
|
| 1584 |
raise HTTPException(status_code=400, detail=f"{col} aceita inteiros de {int(min_v)} a {int(max_v)}")
|
| 1585 |
if col in session.percentuais and (valor < 0 or valor > 1):
|
| 1586 |
raise HTTPException(status_code=400, detail=f"{col} aceita valores entre 0 e 1")
|
backend/app/services/pesquisa_service.py
CHANGED
|
@@ -16,6 +16,7 @@ from fastapi import HTTPException
|
|
| 16 |
from joblib import load
|
| 17 |
|
| 18 |
from app.core.elaboracao.core import _migrar_pacote_v1_para_v2
|
|
|
|
| 19 |
from app.services import model_repository
|
| 20 |
from app.services.serializers import sanitize_value
|
| 21 |
|
|
@@ -168,6 +169,7 @@ class PesquisaFiltros:
|
|
| 168 |
rh_max: float | None = None
|
| 169 |
aval_finalidade: str | None = None
|
| 170 |
aval_finalidade_colunas: list[str] | None = None
|
|
|
|
| 171 |
aval_bairro: str | None = None
|
| 172 |
aval_bairro_colunas: list[str] | None = None
|
| 173 |
aval_endereco: str | None = None
|
|
@@ -292,6 +294,7 @@ def listar_modelos(filtros: PesquisaFiltros, limite: int | None = None, somente_
|
|
| 292 |
"otica": otica,
|
| 293 |
"aval_finalidade": filtros.aval_finalidade,
|
| 294 |
"aval_finalidade_colunas": filtros.aval_finalidade_colunas or [],
|
|
|
|
| 295 |
"aval_bairro": filtros.aval_bairro,
|
| 296 |
"aval_bairro_colunas": filtros.aval_bairro_colunas or [],
|
| 297 |
"aval_endereco": filtros.aval_endereco,
|
|
@@ -358,6 +361,7 @@ def listar_modelos(filtros: PesquisaFiltros, limite: int | None = None, somente_
|
|
| 358 |
"otica": otica,
|
| 359 |
"aval_finalidade": filtros.aval_finalidade,
|
| 360 |
"aval_finalidade_colunas": filtros.aval_finalidade_colunas or [],
|
|
|
|
| 361 |
"aval_bairro": filtros.aval_bairro,
|
| 362 |
"aval_bairro_colunas": filtros.aval_bairro_colunas or [],
|
| 363 |
"aval_endereco": filtros.aval_endereco,
|
|
@@ -446,6 +450,7 @@ def gerar_mapa_modelos(modelos_ids: list[str], limite_pontos_por_modelo: int = 4
|
|
| 446 |
)
|
| 447 |
folium.TileLayer(tiles="OpenStreetMap", name="OpenStreetMap", control=True, show=True).add_to(mapa)
|
| 448 |
folium.TileLayer(tiles="CartoDB positron", name="Positron", control=True, show=False).add_to(mapa)
|
|
|
|
| 449 |
|
| 450 |
total_pontos = 0
|
| 451 |
for modelo in modelos_plotados:
|
|
@@ -1144,6 +1149,19 @@ def _anexar_avaliando_info(
|
|
| 1144 |
detalhe = "nenhum dos bairros informados esta na cobertura do modelo" if len(termos_bairro) > 1 else "bairro fora da cobertura do modelo"
|
| 1145 |
registrar("bairro", bairro_info, aceito, detalhe)
|
| 1146 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1147 |
endereco_info = filtros.aval_endereco
|
| 1148 |
if _is_provided(endereco_info):
|
| 1149 |
candidatos = [item.get("endereco_referencia"), ", ".join(item.get("bairros") or [])]
|
|
@@ -1232,6 +1250,7 @@ def _extrair_sugestoes(
|
|
| 1232 |
bairros: list[str] = []
|
| 1233 |
enderecos: list[str] = []
|
| 1234 |
tipos_modelo: list[str] = []
|
|
|
|
| 1235 |
|
| 1236 |
fontes_finalidade = _dedupe_strings((fontes_admin.get("finalidade") or []) + (fontes_admin.get("aval_finalidade") or []))
|
| 1237 |
fontes_bairro = _dedupe_strings((fontes_admin.get("bairros") or []) + (fontes_admin.get("aval_bairro") or []))
|
|
@@ -1251,6 +1270,7 @@ def _extrair_sugestoes(
|
|
| 1251 |
bairros.extend([str(item) for item in (modelo.get("bairros") or [])])
|
| 1252 |
enderecos.append(str(modelo.get("endereco_referencia") or ""))
|
| 1253 |
tipos_modelo.append(str(_tipo_modelo_modelo(modelo) or ""))
|
|
|
|
| 1254 |
|
| 1255 |
return {
|
| 1256 |
"nomes_modelo": _lista_textos_unicos(nomes, limite),
|
|
@@ -1259,6 +1279,7 @@ def _extrair_sugestoes(
|
|
| 1259 |
"bairros": _lista_textos_unicos(bairros, limite),
|
| 1260 |
"enderecos": _lista_textos_unicos(enderecos, limite),
|
| 1261 |
"tipos_modelo": _lista_textos_unicos(tipos_modelo, limite),
|
|
|
|
| 1262 |
}
|
| 1263 |
|
| 1264 |
|
|
@@ -1770,6 +1791,72 @@ def _negociacao_modelo_modelo(modelo: dict[str, Any]) -> str | None:
|
|
| 1770 |
return None
|
| 1771 |
|
| 1772 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1773 |
def _extrair_termos_bairro(filtros: PesquisaFiltros) -> list[str]:
|
| 1774 |
termos: list[str] = []
|
| 1775 |
if filtros.bairro:
|
|
|
|
| 16 |
from joblib import load
|
| 17 |
|
| 18 |
from app.core.elaboracao.core import _migrar_pacote_v1_para_v2
|
| 19 |
+
from app.core.map_layers import add_bairros_layer
|
| 20 |
from app.services import model_repository
|
| 21 |
from app.services.serializers import sanitize_value
|
| 22 |
|
|
|
|
| 169 |
rh_max: float | None = None
|
| 170 |
aval_finalidade: str | None = None
|
| 171 |
aval_finalidade_colunas: list[str] | None = None
|
| 172 |
+
aval_zona: str | None = None
|
| 173 |
aval_bairro: str | None = None
|
| 174 |
aval_bairro_colunas: list[str] | None = None
|
| 175 |
aval_endereco: str | None = None
|
|
|
|
| 294 |
"otica": otica,
|
| 295 |
"aval_finalidade": filtros.aval_finalidade,
|
| 296 |
"aval_finalidade_colunas": filtros.aval_finalidade_colunas or [],
|
| 297 |
+
"aval_zona": filtros.aval_zona,
|
| 298 |
"aval_bairro": filtros.aval_bairro,
|
| 299 |
"aval_bairro_colunas": filtros.aval_bairro_colunas or [],
|
| 300 |
"aval_endereco": filtros.aval_endereco,
|
|
|
|
| 361 |
"otica": otica,
|
| 362 |
"aval_finalidade": filtros.aval_finalidade,
|
| 363 |
"aval_finalidade_colunas": filtros.aval_finalidade_colunas or [],
|
| 364 |
+
"aval_zona": filtros.aval_zona,
|
| 365 |
"aval_bairro": filtros.aval_bairro,
|
| 366 |
"aval_bairro_colunas": filtros.aval_bairro_colunas or [],
|
| 367 |
"aval_endereco": filtros.aval_endereco,
|
|
|
|
| 450 |
)
|
| 451 |
folium.TileLayer(tiles="OpenStreetMap", name="OpenStreetMap", control=True, show=True).add_to(mapa)
|
| 452 |
folium.TileLayer(tiles="CartoDB positron", name="Positron", control=True, show=False).add_to(mapa)
|
| 453 |
+
add_bairros_layer(mapa, show=True)
|
| 454 |
|
| 455 |
total_pontos = 0
|
| 456 |
for modelo in modelos_plotados:
|
|
|
|
| 1149 |
detalhe = "nenhum dos bairros informados esta na cobertura do modelo" if len(termos_bairro) > 1 else "bairro fora da cobertura do modelo"
|
| 1150 |
registrar("bairro", bairro_info, aceito, detalhe)
|
| 1151 |
|
| 1152 |
+
zona_info = filtros.aval_zona
|
| 1153 |
+
if _is_provided(zona_info):
|
| 1154 |
+
zonas_informadas = _extrair_termos_zona(str(zona_info))
|
| 1155 |
+
zonas_modelo = _zonas_avaliacao_modelo(item)
|
| 1156 |
+
zonas_modelo_norm = {_normalize(zona) for zona in zonas_modelo}
|
| 1157 |
+
aceito = any(_normalize(zona) in zonas_modelo_norm for zona in zonas_informadas) if zonas_informadas else False
|
| 1158 |
+
detalhe = (
|
| 1159 |
+
"nenhuma das zonas informadas foi encontrada no modelo"
|
| 1160 |
+
if len(zonas_informadas) > 1
|
| 1161 |
+
else "zona fora da cobertura do modelo"
|
| 1162 |
+
)
|
| 1163 |
+
registrar("zona_avaliacao", zona_info, aceito, detalhe)
|
| 1164 |
+
|
| 1165 |
endereco_info = filtros.aval_endereco
|
| 1166 |
if _is_provided(endereco_info):
|
| 1167 |
candidatos = [item.get("endereco_referencia"), ", ".join(item.get("bairros") or [])]
|
|
|
|
| 1250 |
bairros: list[str] = []
|
| 1251 |
enderecos: list[str] = []
|
| 1252 |
tipos_modelo: list[str] = []
|
| 1253 |
+
zonas_avaliacao: list[str] = []
|
| 1254 |
|
| 1255 |
fontes_finalidade = _dedupe_strings((fontes_admin.get("finalidade") or []) + (fontes_admin.get("aval_finalidade") or []))
|
| 1256 |
fontes_bairro = _dedupe_strings((fontes_admin.get("bairros") or []) + (fontes_admin.get("aval_bairro") or []))
|
|
|
|
| 1270 |
bairros.extend([str(item) for item in (modelo.get("bairros") or [])])
|
| 1271 |
enderecos.append(str(modelo.get("endereco_referencia") or ""))
|
| 1272 |
tipos_modelo.append(str(_tipo_modelo_modelo(modelo) or ""))
|
| 1273 |
+
zonas_avaliacao.extend(_zonas_avaliacao_modelo(modelo))
|
| 1274 |
|
| 1275 |
return {
|
| 1276 |
"nomes_modelo": _lista_textos_unicos(nomes, limite),
|
|
|
|
| 1279 |
"bairros": _lista_textos_unicos(bairros, limite),
|
| 1280 |
"enderecos": _lista_textos_unicos(enderecos, limite),
|
| 1281 |
"tipos_modelo": _lista_textos_unicos(tipos_modelo, limite),
|
| 1282 |
+
"zonas_avaliacao": _lista_zonas_unicas(zonas_avaliacao, limite),
|
| 1283 |
}
|
| 1284 |
|
| 1285 |
|
|
|
|
| 1791 |
return None
|
| 1792 |
|
| 1793 |
|
| 1794 |
+
def _normalizar_token_zona(value: Any) -> str | None:
|
| 1795 |
+
texto = _str_or_none(value)
|
| 1796 |
+
if not texto:
|
| 1797 |
+
return None
|
| 1798 |
+
match = re.search(r"Z?\s*0*(\d+)", texto.upper())
|
| 1799 |
+
if not match:
|
| 1800 |
+
return None
|
| 1801 |
+
numero = int(match.group(1))
|
| 1802 |
+
if numero <= 0:
|
| 1803 |
+
return None
|
| 1804 |
+
return f"Z{numero}"
|
| 1805 |
+
|
| 1806 |
+
|
| 1807 |
+
def _extrair_termos_zona(texto: str) -> list[str]:
|
| 1808 |
+
zonas = []
|
| 1809 |
+
vistos = set()
|
| 1810 |
+
for termo in _split_terms(texto):
|
| 1811 |
+
zona = _normalizar_token_zona(termo)
|
| 1812 |
+
if not zona:
|
| 1813 |
+
continue
|
| 1814 |
+
chave = _normalize(zona)
|
| 1815 |
+
if chave in vistos:
|
| 1816 |
+
continue
|
| 1817 |
+
vistos.add(chave)
|
| 1818 |
+
zonas.append(zona)
|
| 1819 |
+
return zonas
|
| 1820 |
+
|
| 1821 |
+
|
| 1822 |
+
def _zonas_avaliacao_modelo(modelo: dict[str, Any]) -> list[str]:
|
| 1823 |
+
nomes_ref = [
|
| 1824 |
+
_str_or_none(modelo.get("arquivo")) or "",
|
| 1825 |
+
_str_or_none(modelo.get("nome_modelo")) or "",
|
| 1826 |
+
]
|
| 1827 |
+
zonas = []
|
| 1828 |
+
vistos = set()
|
| 1829 |
+
for nome in nomes_ref:
|
| 1830 |
+
nome_upper = nome.upper()
|
| 1831 |
+
for match in re.finditer(r"(?:^|_)Z(\d+)(?=_|$)", nome_upper):
|
| 1832 |
+
zona = _normalizar_token_zona(f"Z{match.group(1)}")
|
| 1833 |
+
if not zona:
|
| 1834 |
+
continue
|
| 1835 |
+
chave = _normalize(zona)
|
| 1836 |
+
if chave in vistos:
|
| 1837 |
+
continue
|
| 1838 |
+
vistos.add(chave)
|
| 1839 |
+
zonas.append(zona)
|
| 1840 |
+
return sorted(zonas, key=lambda item: int(re.search(r"\d+", item).group(0)))
|
| 1841 |
+
|
| 1842 |
+
|
| 1843 |
+
def _lista_zonas_unicas(valores: list[str], limite: int = 200) -> list[str]:
|
| 1844 |
+
unicas = []
|
| 1845 |
+
vistos = set()
|
| 1846 |
+
for valor in valores:
|
| 1847 |
+
zona = _normalizar_token_zona(valor)
|
| 1848 |
+
if not zona:
|
| 1849 |
+
continue
|
| 1850 |
+
chave = _normalize(zona)
|
| 1851 |
+
if not chave or chave in vistos:
|
| 1852 |
+
continue
|
| 1853 |
+
vistos.add(chave)
|
| 1854 |
+
unicas.append(zona)
|
| 1855 |
+
if len(unicas) >= limite:
|
| 1856 |
+
break
|
| 1857 |
+
return sorted(unicas, key=lambda item: int(re.search(r"\d+", item).group(0)))
|
| 1858 |
+
|
| 1859 |
+
|
| 1860 |
def _extrair_termos_bairro(filtros: PesquisaFiltros) -> list[str]:
|
| 1861 |
termos: list[str] = []
|
| 1862 |
if filtros.bairro:
|
backend/app/services/visualizacao_service.py
CHANGED
|
@@ -20,6 +20,10 @@ from app.services.serializers import dataframe_to_payload, figure_to_payload, sa
|
|
| 20 |
CHAVES_ESPERADAS = ["versao", "dados", "transformacoes", "modelo"]
|
| 21 |
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
def listar_modelos_repositorio() -> dict[str, Any]:
|
| 24 |
return sanitize_value(model_repository.list_repository_models())
|
| 25 |
|
|
@@ -294,7 +298,10 @@ def calcular_avaliacao(session: SessionState, valores_x: dict[str, Any], indice_
|
|
| 294 |
if col in info["codigo_alocado"] and col in est_idx.index:
|
| 295 |
min_v = float(est_idx.loc[col, "Mínimo"])
|
| 296 |
max_v = float(est_idx.loc[col, "Máximo"])
|
| 297 |
-
if
|
|
|
|
|
|
|
|
|
|
| 298 |
raise HTTPException(status_code=400, detail=f"{col} aceita inteiros de {int(min_v)} a {int(max_v)}")
|
| 299 |
if col in info["percentuais"] and (valor < 0 or valor > 1):
|
| 300 |
raise HTTPException(status_code=400, detail=f"{col} aceita valores entre 0 e 1")
|
|
|
|
| 20 |
CHAVES_ESPERADAS = ["versao", "dados", "transformacoes", "modelo"]
|
| 21 |
|
| 22 |
|
| 23 |
+
def _is_rh_col(coluna: str) -> bool:
|
| 24 |
+
return str(coluna or "").strip().upper() == "RH"
|
| 25 |
+
|
| 26 |
+
|
| 27 |
def listar_modelos_repositorio() -> dict[str, Any]:
|
| 28 |
return sanitize_value(model_repository.list_repository_models())
|
| 29 |
|
|
|
|
| 298 |
if col in info["codigo_alocado"] and col in est_idx.index:
|
| 299 |
min_v = float(est_idx.loc[col, "Mínimo"])
|
| 300 |
max_v = float(est_idx.loc[col, "Máximo"])
|
| 301 |
+
if _is_rh_col(col):
|
| 302 |
+
if float(valor) != int(float(valor)):
|
| 303 |
+
raise HTTPException(status_code=400, detail=f"{col} aceita apenas valores inteiros")
|
| 304 |
+
elif float(valor) != int(float(valor)) or valor < min_v or valor > max_v:
|
| 305 |
raise HTTPException(status_code=400, detail=f"{col} aceita inteiros de {int(min_v)} a {int(max_v)}")
|
| 306 |
if col in info["percentuais"] and (valor < 0 or valor > 1):
|
| 307 |
raise HTTPException(status_code=400, detail=f"{col} aceita valores entre 0 e 1")
|
frontend/src/api.js
CHANGED
|
@@ -143,10 +143,12 @@ export const api = {
|
|
| 143 |
},
|
| 144 |
classifyElaboracaoX: (sessionId, colunasX) => postJson('/api/elaboracao/classify-x', { session_id: sessionId, colunas_x: colunasX }),
|
| 145 |
|
| 146 |
-
searchTransformations: (sessionId, grauCoef, grauF) => postJson('/api/elaboracao/search-transformations', {
|
| 147 |
session_id: sessionId,
|
| 148 |
grau_min_coef: grauCoef,
|
| 149 |
grau_min_f: grauF,
|
|
|
|
|
|
|
| 150 |
}),
|
| 151 |
|
| 152 |
adoptSuggestion: (sessionId, indice) => postJson('/api/elaboracao/adopt-suggestion', { session_id: sessionId, indice }),
|
|
|
|
| 143 |
},
|
| 144 |
classifyElaboracaoX: (sessionId, colunasX) => postJson('/api/elaboracao/classify-x', { session_id: sessionId, colunas_x: colunasX }),
|
| 145 |
|
| 146 |
+
searchTransformations: (sessionId, grauCoef, grauF, transformacoesFixas = {}, transformacaoYFixa = 'Livre') => postJson('/api/elaboracao/search-transformations', {
|
| 147 |
session_id: sessionId,
|
| 148 |
grau_min_coef: grauCoef,
|
| 149 |
grau_min_f: grauF,
|
| 150 |
+
transformacoes_fixas: transformacoesFixas,
|
| 151 |
+
transformacao_y_fixa: transformacaoYFixa,
|
| 152 |
}),
|
| 153 |
|
| 154 |
adoptSuggestion: (sessionId, indice) => postJson('/api/elaboracao/adopt-suggestion', { session_id: sessionId, indice }),
|
frontend/src/components/ElaboracaoTab.jsx
CHANGED
|
@@ -7,6 +7,7 @@ import LoadingOverlay from './LoadingOverlay'
|
|
| 7 |
import MapFrame from './MapFrame'
|
| 8 |
import PlotFigure from './PlotFigure'
|
| 9 |
import SectionBlock from './SectionBlock'
|
|
|
|
| 10 |
|
| 11 |
const OPERADORES = ['<=', '>=', '<', '>', '=']
|
| 12 |
const GRAUS_COEF = [
|
|
@@ -68,6 +69,27 @@ function buildSelectionSnapshot(payload = {}) {
|
|
| 68 |
})
|
| 69 |
}
|
| 70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
function buildGrauSnapshot(grauCoef, grauF) {
|
| 72 |
return `${Number(grauCoef) || 0}|${Number(grauF) || 0}`
|
| 73 |
}
|
|
@@ -683,10 +705,13 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 683 |
|
| 684 |
const [transformacaoY, setTransformacaoY] = useState('(x)')
|
| 685 |
const [transformacoesX, setTransformacoesX] = useState({})
|
|
|
|
|
|
|
| 686 |
const [manualTransformPreview, setManualTransformPreview] = useState(null)
|
| 687 |
const [manualTransformPreviewLoading, setManualTransformPreviewLoading] = useState(false)
|
| 688 |
const [transformacoesAplicadas, setTransformacoesAplicadas] = useState(null)
|
| 689 |
const [origemTransformacoes, setOrigemTransformacoes] = useState(null)
|
|
|
|
| 690 |
const [section10ManualOpen, setSection10ManualOpen] = useState(false)
|
| 691 |
const [section6EditOpen, setSection6EditOpen] = useState(true)
|
| 692 |
|
|
@@ -711,7 +736,7 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 711 |
const [baseChoices, setBaseChoices] = useState([])
|
| 712 |
const [baseValue, setBaseValue] = useState('')
|
| 713 |
|
| 714 |
-
const [nomeArquivoExport, setNomeArquivoExport] = useState('
|
| 715 |
const [avaliadores, setAvaliadores] = useState([])
|
| 716 |
const [avaliadorSelecionado, setAvaliadorSelecionado] = useState('')
|
| 717 |
const [tipoFonteDados, setTipoFonteDados] = useState('')
|
|
@@ -760,6 +785,27 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 760 |
transformacao: formatTransformacaoBadge(transformacoes[coluna]),
|
| 761 |
}))
|
| 762 |
}, [modeloCarregadoInfo])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 763 |
const periodoModeloCarregadoTexto = useMemo(
|
| 764 |
() => formatPeriodoDadosMercado(modeloCarregadoInfo?.periodo_dados_mercado),
|
| 765 |
[modeloCarregadoInfo],
|
|
@@ -1343,10 +1389,13 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1343 |
setSelectionAppliedSnapshot(buildSelectionSnapshot({ coluna_y: '' }))
|
| 1344 |
setTransformacaoY('(x)')
|
| 1345 |
setTransformacoesX({})
|
|
|
|
|
|
|
| 1346 |
setManualTransformPreview(null)
|
| 1347 |
setManualTransformPreviewLoading(false)
|
| 1348 |
setTransformacoesAplicadas(null)
|
| 1349 |
setOrigemTransformacoes(null)
|
|
|
|
| 1350 |
setBuscaTransformAppliedSnapshot(buildGrauSnapshot(grauCoef, grauF))
|
| 1351 |
setManualTransformAppliedSnapshot(buildTransformacoesSnapshot('(x)', {}))
|
| 1352 |
setOutliersAnteriores([])
|
|
@@ -1396,6 +1445,7 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1396 |
function applySelectionResponse(resp) {
|
| 1397 |
setSelection(resp)
|
| 1398 |
setSection6EditOpen(false)
|
|
|
|
| 1399 |
setSection10ManualOpen(false)
|
| 1400 |
setManualTransformPreview(null)
|
| 1401 |
setManualTransformPreviewLoading(false)
|
|
@@ -1411,6 +1461,14 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1411 |
map[field.coluna] = field.valor || '(x)'
|
| 1412 |
})
|
| 1413 |
setTransformacoesX(map)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1414 |
setManualTransformAppliedSnapshot(buildTransformacoesSnapshot(transformacaoYAplicada, map))
|
| 1415 |
|
| 1416 |
const grauCoefAplicado = resp.busca?.grau_coef ?? grauCoef
|
|
@@ -1493,12 +1551,8 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1493 |
setAvaliadorSelecionado(resp.elaborador?.nome_completo || '')
|
| 1494 |
const fileName = String(meta.fileName || uploadedFile?.name || arquivoCarregadoInfo?.nome_arquivo || '').trim()
|
| 1495 |
const sheetName = String(meta.sheetName || resp?.sheet_selected || '').trim()
|
| 1496 |
-
|
| 1497 |
-
|
| 1498 |
-
} else {
|
| 1499 |
-
const infoArquivo = buildArquivoCarregadoInfo(resp, { fileName, sheetName })
|
| 1500 |
-
setArquivoCarregadoInfo(infoArquivo)
|
| 1501 |
-
}
|
| 1502 |
setImportacaoErro('')
|
| 1503 |
setRequiresSheet(Boolean(resp.requires_sheet))
|
| 1504 |
setSheetOptions(resp.sheets || [])
|
|
@@ -1523,9 +1577,13 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1523 |
}
|
| 1524 |
|
| 1525 |
setTipoFonteDados('tabular')
|
|
|
|
|
|
|
| 1526 |
setColunaY('')
|
| 1527 |
setColunaYDraft('')
|
| 1528 |
setSelection(null)
|
|
|
|
|
|
|
| 1529 |
setFit(null)
|
| 1530 |
setSelectionAppliedSnapshot(buildSelectionSnapshot())
|
| 1531 |
setColunasX([])
|
|
@@ -1534,6 +1592,8 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1534 |
setPercentuais([])
|
| 1535 |
setTransformacaoY('(x)')
|
| 1536 |
setTransformacoesX({})
|
|
|
|
|
|
|
| 1537 |
setTransformacoesAplicadas(null)
|
| 1538 |
setOrigemTransformacoes(null)
|
| 1539 |
setBuscaTransformAppliedSnapshot(buildGrauSnapshot(0, 0))
|
|
@@ -1548,8 +1608,19 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1548 |
setFiltros(defaultFiltros())
|
| 1549 |
setOutliersTexto('')
|
| 1550 |
setReincluirTexto('')
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1551 |
setOutlierFiltrosAplicadosSnapshot(buildFiltrosSnapshot(defaultFiltros()))
|
| 1552 |
setOutlierTextosAplicadosSnapshot(buildOutlierTextSnapshot('', ''))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1553 |
setCamposAvaliacao([])
|
| 1554 |
valoresAvaliacaoRef.current = {}
|
| 1555 |
setAvaliacaoFormVersion((prev) => prev + 1)
|
|
@@ -1750,6 +1821,8 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1750 |
setFit(null)
|
| 1751 |
setTransformacaoY('(x)')
|
| 1752 |
setTransformacoesX({})
|
|
|
|
|
|
|
| 1753 |
setTransformacoesAplicadas(null)
|
| 1754 |
setOrigemTransformacoes(null)
|
| 1755 |
setBuscaTransformAppliedSnapshot(buildGrauSnapshot(grauCoef, grauF))
|
|
@@ -1920,7 +1993,13 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1920 |
async function onSearchTransform() {
|
| 1921 |
if (!sessionId) return
|
| 1922 |
await withBusy(async () => {
|
| 1923 |
-
const busca = await api.searchTransformations(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1924 |
const grauCoefAplicado = busca.grau_coef ?? grauCoef
|
| 1925 |
const grauFAplicado = busca.grau_f ?? grauF
|
| 1926 |
setSelection((prev) => ({ ...prev, busca }))
|
|
@@ -1930,6 +2009,17 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1930 |
})
|
| 1931 |
}
|
| 1932 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1933 |
async function onAdoptSuggestion(idx) {
|
| 1934 |
if (!sessionId) return
|
| 1935 |
await withBusy(async () => {
|
|
@@ -2257,11 +2347,12 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 2257 |
}
|
| 2258 |
|
| 2259 |
async function onExportModel() {
|
| 2260 |
-
|
|
|
|
| 2261 |
await withBusy(async () => {
|
| 2262 |
const avaliadorEscolhido = avaliadores.find((item) => item.nome_completo === avaliadorSelecionado) || null
|
| 2263 |
-
const blob = await api.exportModel(sessionId,
|
| 2264 |
-
downloadBlob(blob, `${
|
| 2265 |
})
|
| 2266 |
}
|
| 2267 |
|
|
@@ -2557,21 +2648,18 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 2557 |
|
| 2558 |
{modeloLoadSource === 'repo' ? (
|
| 2559 |
<div className="row upload-repo-row">
|
| 2560 |
-
<label
|
| 2561 |
-
|
| 2562 |
-
|
| 2563 |
-
|
| 2564 |
-
|
| 2565 |
-
|
| 2566 |
-
|
| 2567 |
-
{
|
| 2568 |
-
|
| 2569 |
-
|
| 2570 |
-
|
| 2571 |
-
|
| 2572 |
-
</option>
|
| 2573 |
-
))}
|
| 2574 |
-
</select>
|
| 2575 |
<div className="row compact upload-repo-actions">
|
| 2576 |
<button type="button" onClick={onCarregarModeloRepositorio} disabled={loading || repoModelosLoading || !repoModeloSelecionado}>
|
| 2577 |
Carregar do repositório
|
|
@@ -3283,15 +3371,15 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 3283 |
</div>
|
| 3284 |
</>
|
| 3285 |
) : null}
|
| 3286 |
-
{
|
| 3287 |
<div className="section6-selected-summary">
|
| 3288 |
<div className="section6-applied-badge">
|
| 3289 |
<div className="section6-applied-badge-title">Variáveis atualmente aplicadas</div>
|
| 3290 |
<div className="section6-summary-group">
|
| 3291 |
<div className="section6-summary-label">Independentes:</div>
|
| 3292 |
-
{
|
| 3293 |
<div className="checkbox-inline-wrap">
|
| 3294 |
-
{
|
| 3295 |
<span key={`selected-x-${coluna}`} className="compact-chip">{coluna}</span>
|
| 3296 |
))}
|
| 3297 |
</div>
|
|
@@ -3301,9 +3389,9 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 3301 |
</div>
|
| 3302 |
<div className="section6-summary-group">
|
| 3303 |
<div className="section6-summary-label">Dicotômicas:</div>
|
| 3304 |
-
{dicotomicas.length > 0 ? (
|
| 3305 |
<div className="checkbox-inline-wrap">
|
| 3306 |
-
{dicotomicas.map((coluna) => (
|
| 3307 |
<span key={`selected-d-${coluna}`} className="compact-chip">{coluna}</span>
|
| 3308 |
))}
|
| 3309 |
</div>
|
|
@@ -3313,9 +3401,9 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 3313 |
</div>
|
| 3314 |
<div className="section6-summary-group">
|
| 3315 |
<div className="section6-summary-label">Variáveis categóricas codificadas:</div>
|
| 3316 |
-
{
|
| 3317 |
<div className="checkbox-inline-wrap">
|
| 3318 |
-
{
|
| 3319 |
<span key={`selected-c-${coluna}`} className="compact-chip">{coluna}</span>
|
| 3320 |
))}
|
| 3321 |
</div>
|
|
@@ -3325,9 +3413,9 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 3325 |
</div>
|
| 3326 |
<div className="section6-summary-group">
|
| 3327 |
<div className="section6-summary-label">Percentuais:</div>
|
| 3328 |
-
{percentuais.length > 0 ? (
|
| 3329 |
<div className="checkbox-inline-wrap">
|
| 3330 |
-
{percentuais.map((coluna) => (
|
| 3331 |
<span key={`selected-p-${coluna}`} className="compact-chip">{coluna}</span>
|
| 3332 |
))}
|
| 3333 |
</div>
|
|
@@ -3423,20 +3511,75 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 3423 |
</SectionBlock>
|
| 3424 |
|
| 3425 |
<SectionBlock step="11" title="Transformações Sugeridas" subtitle="Busca automática de combinações por R² e enquadramento.">
|
| 3426 |
-
<div className="
|
| 3427 |
-
<
|
| 3428 |
-
|
| 3429 |
-
{
|
| 3430 |
-
|
| 3431 |
-
|
| 3432 |
-
|
| 3433 |
-
<
|
| 3434 |
-
|
| 3435 |
-
{
|
| 3436 |
-
|
| 3437 |
-
|
| 3438 |
-
|
| 3439 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3440 |
</div>
|
| 3441 |
{(selection.busca?.resultados || []).length > 0 ? (
|
| 3442 |
<div className="transform-suggestions-grid">
|
|
@@ -4098,7 +4241,12 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 4098 |
<SectionBlock step="19" title="Exportar Modelo" subtitle="Geração do pacote .dai e download da base tratada.">
|
| 4099 |
<div className="row">
|
| 4100 |
<label>Nome do arquivo (.dai)</label>
|
| 4101 |
-
<input
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4102 |
<label>Avaliador</label>
|
| 4103 |
<select value={avaliadorSelecionado} onChange={(e) => setAvaliadorSelecionado(e.target.value)}>
|
| 4104 |
<option value="">Manter elaborador do modelo (se houver)</option>
|
|
@@ -4108,7 +4256,7 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 4108 |
</option>
|
| 4109 |
))}
|
| 4110 |
</select>
|
| 4111 |
-
<button onClick={onExportModel} disabled={loading || !nomeArquivoExport}>Exportar modelo</button>
|
| 4112 |
<button onClick={onExportBase} disabled={loading}>Exportar base CSV</button>
|
| 4113 |
</div>
|
| 4114 |
</SectionBlock>
|
|
|
|
| 7 |
import MapFrame from './MapFrame'
|
| 8 |
import PlotFigure from './PlotFigure'
|
| 9 |
import SectionBlock from './SectionBlock'
|
| 10 |
+
import SinglePillAutocomplete from './SinglePillAutocomplete'
|
| 11 |
|
| 12 |
const OPERADORES = ['<=', '>=', '<', '>', '=']
|
| 13 |
const GRAUS_COEF = [
|
|
|
|
| 69 |
})
|
| 70 |
}
|
| 71 |
|
| 72 |
+
function parseSelectionSnapshot(snapshot) {
|
| 73 |
+
try {
|
| 74 |
+
const parsed = JSON.parse(String(snapshot || '{}'))
|
| 75 |
+
return {
|
| 76 |
+
coluna_y: String(parsed?.coluna_y || '').trim(),
|
| 77 |
+
colunas_x: normalizeSnapshotArray(parsed?.colunas_x),
|
| 78 |
+
dicotomicas: normalizeSnapshotArray(parsed?.dicotomicas),
|
| 79 |
+
codigo_alocado: normalizeSnapshotArray(parsed?.codigo_alocado),
|
| 80 |
+
percentuais: normalizeSnapshotArray(parsed?.percentuais),
|
| 81 |
+
}
|
| 82 |
+
} catch {
|
| 83 |
+
return {
|
| 84 |
+
coluna_y: '',
|
| 85 |
+
colunas_x: [],
|
| 86 |
+
dicotomicas: [],
|
| 87 |
+
codigo_alocado: [],
|
| 88 |
+
percentuais: [],
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
function buildGrauSnapshot(grauCoef, grauF) {
|
| 94 |
return `${Number(grauCoef) || 0}|${Number(grauF) || 0}`
|
| 95 |
}
|
|
|
|
| 705 |
|
| 706 |
const [transformacaoY, setTransformacaoY] = useState('(x)')
|
| 707 |
const [transformacoesX, setTransformacoesX] = useState({})
|
| 708 |
+
const [transformacaoYFixaBusca, setTransformacaoYFixaBusca] = useState('Livre')
|
| 709 |
+
const [transformacoesFixasBusca, setTransformacoesFixasBusca] = useState({})
|
| 710 |
const [manualTransformPreview, setManualTransformPreview] = useState(null)
|
| 711 |
const [manualTransformPreviewLoading, setManualTransformPreviewLoading] = useState(false)
|
| 712 |
const [transformacoesAplicadas, setTransformacoesAplicadas] = useState(null)
|
| 713 |
const [origemTransformacoes, setOrigemTransformacoes] = useState(null)
|
| 714 |
+
const [section11LocksOpen, setSection11LocksOpen] = useState(false)
|
| 715 |
const [section10ManualOpen, setSection10ManualOpen] = useState(false)
|
| 716 |
const [section6EditOpen, setSection6EditOpen] = useState(true)
|
| 717 |
|
|
|
|
| 736 |
const [baseChoices, setBaseChoices] = useState([])
|
| 737 |
const [baseValue, setBaseValue] = useState('')
|
| 738 |
|
| 739 |
+
const [nomeArquivoExport, setNomeArquivoExport] = useState('')
|
| 740 |
const [avaliadores, setAvaliadores] = useState([])
|
| 741 |
const [avaliadorSelecionado, setAvaliadorSelecionado] = useState('')
|
| 742 |
const [tipoFonteDados, setTipoFonteDados] = useState('')
|
|
|
|
| 785 |
transformacao: formatTransformacaoBadge(transformacoes[coluna]),
|
| 786 |
}))
|
| 787 |
}, [modeloCarregadoInfo])
|
| 788 |
+
const repoModeloOptions = useMemo(
|
| 789 |
+
() => (repoModelos || []).map((item) => ({
|
| 790 |
+
value: String(item?.id || ''),
|
| 791 |
+
label: String(item?.nome_modelo || item?.arquivo || item?.id || ''),
|
| 792 |
+
secondary: String(item?.arquivo || ''),
|
| 793 |
+
})).filter((item) => item.value && item.label),
|
| 794 |
+
[repoModelos],
|
| 795 |
+
)
|
| 796 |
+
const selecaoAplicada = useMemo(() => {
|
| 797 |
+
const contexto = selection?.contexto
|
| 798 |
+
if (contexto && typeof contexto === 'object') {
|
| 799 |
+
return {
|
| 800 |
+
coluna_y: String(contexto.coluna_y || '').trim(),
|
| 801 |
+
colunas_x: normalizeSnapshotArray(contexto.colunas_x),
|
| 802 |
+
dicotomicas: normalizeSnapshotArray(contexto.dicotomicas),
|
| 803 |
+
codigo_alocado: normalizeSnapshotArray(contexto.codigo_alocado),
|
| 804 |
+
percentuais: normalizeSnapshotArray(contexto.percentuais),
|
| 805 |
+
}
|
| 806 |
+
}
|
| 807 |
+
return parseSelectionSnapshot(selectionAppliedSnapshot)
|
| 808 |
+
}, [selection, selectionAppliedSnapshot])
|
| 809 |
const periodoModeloCarregadoTexto = useMemo(
|
| 810 |
() => formatPeriodoDadosMercado(modeloCarregadoInfo?.periodo_dados_mercado),
|
| 811 |
[modeloCarregadoInfo],
|
|
|
|
| 1389 |
setSelectionAppliedSnapshot(buildSelectionSnapshot({ coluna_y: '' }))
|
| 1390 |
setTransformacaoY('(x)')
|
| 1391 |
setTransformacoesX({})
|
| 1392 |
+
setTransformacaoYFixaBusca('Livre')
|
| 1393 |
+
setTransformacoesFixasBusca({})
|
| 1394 |
setManualTransformPreview(null)
|
| 1395 |
setManualTransformPreviewLoading(false)
|
| 1396 |
setTransformacoesAplicadas(null)
|
| 1397 |
setOrigemTransformacoes(null)
|
| 1398 |
+
setSection11LocksOpen(false)
|
| 1399 |
setBuscaTransformAppliedSnapshot(buildGrauSnapshot(grauCoef, grauF))
|
| 1400 |
setManualTransformAppliedSnapshot(buildTransformacoesSnapshot('(x)', {}))
|
| 1401 |
setOutliersAnteriores([])
|
|
|
|
| 1445 |
function applySelectionResponse(resp) {
|
| 1446 |
setSelection(resp)
|
| 1447 |
setSection6EditOpen(false)
|
| 1448 |
+
setSection11LocksOpen(false)
|
| 1449 |
setSection10ManualOpen(false)
|
| 1450 |
setManualTransformPreview(null)
|
| 1451 |
setManualTransformPreviewLoading(false)
|
|
|
|
| 1461 |
map[field.coluna] = field.valor || '(x)'
|
| 1462 |
})
|
| 1463 |
setTransformacoesX(map)
|
| 1464 |
+
setTransformacaoYFixaBusca('Livre')
|
| 1465 |
+
const travasBusca = {}
|
| 1466 |
+
;(resp.transform_fields || []).forEach((field) => {
|
| 1467 |
+
const coluna = String(field?.coluna || '').trim()
|
| 1468 |
+
if (!coluna) return
|
| 1469 |
+
travasBusca[coluna] = 'Livre'
|
| 1470 |
+
})
|
| 1471 |
+
setTransformacoesFixasBusca(travasBusca)
|
| 1472 |
setManualTransformAppliedSnapshot(buildTransformacoesSnapshot(transformacaoYAplicada, map))
|
| 1473 |
|
| 1474 |
const grauCoefAplicado = resp.busca?.grau_coef ?? grauCoef
|
|
|
|
| 1551 |
setAvaliadorSelecionado(resp.elaborador?.nome_completo || '')
|
| 1552 |
const fileName = String(meta.fileName || uploadedFile?.name || arquivoCarregadoInfo?.nome_arquivo || '').trim()
|
| 1553 |
const sheetName = String(meta.sheetName || resp?.sheet_selected || '').trim()
|
| 1554 |
+
const infoArquivo = buildArquivoCarregadoInfo(resp, { fileName, sheetName })
|
| 1555 |
+
setArquivoCarregadoInfo(infoArquivo)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1556 |
setImportacaoErro('')
|
| 1557 |
setRequiresSheet(Boolean(resp.requires_sheet))
|
| 1558 |
setSheetOptions(resp.sheets || [])
|
|
|
|
| 1577 |
}
|
| 1578 |
|
| 1579 |
setTipoFonteDados('tabular')
|
| 1580 |
+
setDados(null)
|
| 1581 |
+
setColunasNumericas([])
|
| 1582 |
setColunaY('')
|
| 1583 |
setColunaYDraft('')
|
| 1584 |
setSelection(null)
|
| 1585 |
+
setGrauCoef(0)
|
| 1586 |
+
setGrauF(0)
|
| 1587 |
setFit(null)
|
| 1588 |
setSelectionAppliedSnapshot(buildSelectionSnapshot())
|
| 1589 |
setColunasX([])
|
|
|
|
| 1592 |
setPercentuais([])
|
| 1593 |
setTransformacaoY('(x)')
|
| 1594 |
setTransformacoesX({})
|
| 1595 |
+
setTransformacaoYFixaBusca('Livre')
|
| 1596 |
+
setTransformacoesFixasBusca({})
|
| 1597 |
setTransformacoesAplicadas(null)
|
| 1598 |
setOrigemTransformacoes(null)
|
| 1599 |
setBuscaTransformAppliedSnapshot(buildGrauSnapshot(0, 0))
|
|
|
|
| 1608 |
setFiltros(defaultFiltros())
|
| 1609 |
setOutliersTexto('')
|
| 1610 |
setReincluirTexto('')
|
| 1611 |
+
setResumoOutliers('Excluidos: 0 | A excluir: 0 | A reincluir: 0 | Total: 0')
|
| 1612 |
+
setOutliersHtml('')
|
| 1613 |
+
setOutliersAnteriores([])
|
| 1614 |
+
setIteracao(1)
|
| 1615 |
setOutlierFiltrosAplicadosSnapshot(buildFiltrosSnapshot(defaultFiltros()))
|
| 1616 |
setOutlierTextosAplicadosSnapshot(buildOutlierTextSnapshot('', ''))
|
| 1617 |
+
setCoordsInfo(null)
|
| 1618 |
+
setManualLat('')
|
| 1619 |
+
setManualLon('')
|
| 1620 |
+
setGeoCdlog('')
|
| 1621 |
+
setGeoNum('')
|
| 1622 |
+
setCoordsMode('menu')
|
| 1623 |
+
setConfirmarExclusaoCoords(false)
|
| 1624 |
setCamposAvaliacao([])
|
| 1625 |
valoresAvaliacaoRef.current = {}
|
| 1626 |
setAvaliacaoFormVersion((prev) => prev + 1)
|
|
|
|
| 1821 |
setFit(null)
|
| 1822 |
setTransformacaoY('(x)')
|
| 1823 |
setTransformacoesX({})
|
| 1824 |
+
setTransformacaoYFixaBusca('Livre')
|
| 1825 |
+
setTransformacoesFixasBusca({})
|
| 1826 |
setTransformacoesAplicadas(null)
|
| 1827 |
setOrigemTransformacoes(null)
|
| 1828 |
setBuscaTransformAppliedSnapshot(buildGrauSnapshot(grauCoef, grauF))
|
|
|
|
| 1993 |
async function onSearchTransform() {
|
| 1994 |
if (!sessionId) return
|
| 1995 |
await withBusy(async () => {
|
| 1996 |
+
const busca = await api.searchTransformations(
|
| 1997 |
+
sessionId,
|
| 1998 |
+
grauCoef,
|
| 1999 |
+
grauF,
|
| 2000 |
+
transformacoesFixasBusca,
|
| 2001 |
+
transformacaoYFixaBusca,
|
| 2002 |
+
)
|
| 2003 |
const grauCoefAplicado = busca.grau_coef ?? grauCoef
|
| 2004 |
const grauFAplicado = busca.grau_f ?? grauF
|
| 2005 |
setSelection((prev) => ({ ...prev, busca }))
|
|
|
|
| 2009 |
})
|
| 2010 |
}
|
| 2011 |
|
| 2012 |
+
function onLiberarTravasBusca() {
|
| 2013 |
+
setTransformacaoYFixaBusca('Livre')
|
| 2014 |
+
const travas = {}
|
| 2015 |
+
;(selection?.transform_fields || []).forEach((field) => {
|
| 2016 |
+
const coluna = String(field?.coluna || '').trim()
|
| 2017 |
+
if (!coluna) return
|
| 2018 |
+
travas[coluna] = 'Livre'
|
| 2019 |
+
})
|
| 2020 |
+
setTransformacoesFixasBusca(travas)
|
| 2021 |
+
}
|
| 2022 |
+
|
| 2023 |
async function onAdoptSuggestion(idx) {
|
| 2024 |
if (!sessionId) return
|
| 2025 |
await withBusy(async () => {
|
|
|
|
| 2347 |
}
|
| 2348 |
|
| 2349 |
async function onExportModel() {
|
| 2350 |
+
const nomeExport = String(nomeArquivoExport || '').trim()
|
| 2351 |
+
if (!sessionId || !nomeExport) return
|
| 2352 |
await withBusy(async () => {
|
| 2353 |
const avaliadorEscolhido = avaliadores.find((item) => item.nome_completo === avaliadorSelecionado) || null
|
| 2354 |
+
const blob = await api.exportModel(sessionId, nomeExport, avaliadorEscolhido || elaborador || null)
|
| 2355 |
+
downloadBlob(blob, `${nomeExport.replace(/\.dai$/i, '')}.dai`)
|
| 2356 |
})
|
| 2357 |
}
|
| 2358 |
|
|
|
|
| 2648 |
|
| 2649 |
{modeloLoadSource === 'repo' ? (
|
| 2650 |
<div className="row upload-repo-row">
|
| 2651 |
+
<label className="upload-repo-field">
|
| 2652 |
+
Modelo do repositório
|
| 2653 |
+
<SinglePillAutocomplete
|
| 2654 |
+
value={repoModeloSelecionado}
|
| 2655 |
+
onChange={setRepoModeloSelecionado}
|
| 2656 |
+
options={repoModeloOptions}
|
| 2657 |
+
placeholder={repoModelosLoading ? 'Carregando lista...' : repoModeloOptions.length > 0 ? 'Digite para buscar modelo' : 'Nenhum modelo disponível'}
|
| 2658 |
+
emptyMessage={repoModeloOptions.length > 0 ? 'Nenhum modelo encontrado.' : 'Nenhum modelo disponível.'}
|
| 2659 |
+
loading={repoModelosLoading}
|
| 2660 |
+
disabled={loading || repoModelosLoading || repoModeloOptions.length === 0}
|
| 2661 |
+
/>
|
| 2662 |
+
</label>
|
|
|
|
|
|
|
|
|
|
| 2663 |
<div className="row compact upload-repo-actions">
|
| 2664 |
<button type="button" onClick={onCarregarModeloRepositorio} disabled={loading || repoModelosLoading || !repoModeloSelecionado}>
|
| 2665 |
Carregar do repositório
|
|
|
|
| 3371 |
</div>
|
| 3372 |
</>
|
| 3373 |
) : null}
|
| 3374 |
+
{selection ? (
|
| 3375 |
<div className="section6-selected-summary">
|
| 3376 |
<div className="section6-applied-badge">
|
| 3377 |
<div className="section6-applied-badge-title">Variáveis atualmente aplicadas</div>
|
| 3378 |
<div className="section6-summary-group">
|
| 3379 |
<div className="section6-summary-label">Independentes:</div>
|
| 3380 |
+
{selecaoAplicada.colunas_x.length > 0 ? (
|
| 3381 |
<div className="checkbox-inline-wrap">
|
| 3382 |
+
{selecaoAplicada.colunas_x.map((coluna) => (
|
| 3383 |
<span key={`selected-x-${coluna}`} className="compact-chip">{coluna}</span>
|
| 3384 |
))}
|
| 3385 |
</div>
|
|
|
|
| 3389 |
</div>
|
| 3390 |
<div className="section6-summary-group">
|
| 3391 |
<div className="section6-summary-label">Dicotômicas:</div>
|
| 3392 |
+
{selecaoAplicada.dicotomicas.length > 0 ? (
|
| 3393 |
<div className="checkbox-inline-wrap">
|
| 3394 |
+
{selecaoAplicada.dicotomicas.map((coluna) => (
|
| 3395 |
<span key={`selected-d-${coluna}`} className="compact-chip">{coluna}</span>
|
| 3396 |
))}
|
| 3397 |
</div>
|
|
|
|
| 3401 |
</div>
|
| 3402 |
<div className="section6-summary-group">
|
| 3403 |
<div className="section6-summary-label">Variáveis categóricas codificadas:</div>
|
| 3404 |
+
{selecaoAplicada.codigo_alocado.length > 0 ? (
|
| 3405 |
<div className="checkbox-inline-wrap">
|
| 3406 |
+
{selecaoAplicada.codigo_alocado.map((coluna) => (
|
| 3407 |
<span key={`selected-c-${coluna}`} className="compact-chip">{coluna}</span>
|
| 3408 |
))}
|
| 3409 |
</div>
|
|
|
|
| 3413 |
</div>
|
| 3414 |
<div className="section6-summary-group">
|
| 3415 |
<div className="section6-summary-label">Percentuais:</div>
|
| 3416 |
+
{selecaoAplicada.percentuais.length > 0 ? (
|
| 3417 |
<div className="checkbox-inline-wrap">
|
| 3418 |
+
{selecaoAplicada.percentuais.map((coluna) => (
|
| 3419 |
<span key={`selected-p-${coluna}`} className="compact-chip">{coluna}</span>
|
| 3420 |
))}
|
| 3421 |
</div>
|
|
|
|
| 3511 |
</SectionBlock>
|
| 3512 |
|
| 3513 |
<SectionBlock step="11" title="Transformações Sugeridas" subtitle="Busca automática de combinações por R² e enquadramento.">
|
| 3514 |
+
<div className="manual-transform-toggle section11-toggle-wrap">
|
| 3515 |
+
<button
|
| 3516 |
+
type="button"
|
| 3517 |
+
className={section11LocksOpen ? 'btn-manual-toggle active' : 'btn-manual-toggle'}
|
| 3518 |
+
onClick={() => setSection11LocksOpen((prev) => !prev)}
|
| 3519 |
+
>
|
| 3520 |
+
{section11LocksOpen ? 'Ocultar travas de transformação' : 'Trancar variáveis em transformações'}
|
| 3521 |
+
</button>
|
| 3522 |
+
{section11LocksOpen ? (
|
| 3523 |
+
<button type="button" className="btn-section11-unlock" onClick={onLiberarTravasBusca} disabled={loading}>
|
| 3524 |
+
Liberar todas variáveis
|
| 3525 |
+
</button>
|
| 3526 |
+
) : null}
|
| 3527 |
+
</div>
|
| 3528 |
+
{section11LocksOpen ? (
|
| 3529 |
+
<>
|
| 3530 |
+
<div className="transform-grid">
|
| 3531 |
+
<div className="transform-card transform-card-y">
|
| 3532 |
+
<div className="transform-card-head">
|
| 3533 |
+
<span>{colunaY ? `${colunaY} (Y)` : 'Variável dependente (Y)'}</span>
|
| 3534 |
+
</div>
|
| 3535 |
+
<select
|
| 3536 |
+
value={transformacaoYFixaBusca}
|
| 3537 |
+
onChange={(e) => setTransformacaoYFixaBusca(e.target.value)}
|
| 3538 |
+
>
|
| 3539 |
+
<option value="Livre">Livre</option>
|
| 3540 |
+
{['(x)', '1/(x)', 'ln(x)', 'exp(x)', '(x)^2', 'raiz(x)', '1/raiz(x)'].map((choice) => (
|
| 3541 |
+
<option key={`y-lock-${choice}`} value={choice}>{choice}</option>
|
| 3542 |
+
))}
|
| 3543 |
+
</select>
|
| 3544 |
+
</div>
|
| 3545 |
+
{(selection.transform_fields || [])
|
| 3546 |
+
.filter((field) => !field.locked)
|
| 3547 |
+
.map((field) => (
|
| 3548 |
+
<div key={`tf-lock-${field.coluna}`} className="transform-card">
|
| 3549 |
+
<div className="transform-card-head">
|
| 3550 |
+
<span>{field.coluna}</span>
|
| 3551 |
+
</div>
|
| 3552 |
+
<select
|
| 3553 |
+
value={transformacoesFixasBusca[field.coluna] || 'Livre'}
|
| 3554 |
+
onChange={(e) => setTransformacoesFixasBusca((prev) => ({ ...prev, [field.coluna]: e.target.value }))}
|
| 3555 |
+
>
|
| 3556 |
+
<option value="Livre">Livre</option>
|
| 3557 |
+
{(field.choices || []).map((choice) => (
|
| 3558 |
+
<option key={`${field.coluna}-lock-${choice}`} value={choice}>{choice}</option>
|
| 3559 |
+
))}
|
| 3560 |
+
</select>
|
| 3561 |
+
</div>
|
| 3562 |
+
))}
|
| 3563 |
+
</div>
|
| 3564 |
+
</>
|
| 3565 |
+
) : null}
|
| 3566 |
+
<div className="section11-search-criteria">
|
| 3567 |
+
<div className="section11-search-criteria-title">Critérios da busca</div>
|
| 3568 |
+
<div className="row section11-search-row">
|
| 3569 |
+
<label>Grau mínimo dos coeficientes</label>
|
| 3570 |
+
<select value={grauCoef} onChange={(e) => setGrauCoef(Number(e.target.value))}>
|
| 3571 |
+
{GRAUS_COEF.map((item) => (
|
| 3572 |
+
<option key={`coef-${item.value}`} value={item.value}>{item.label}</option>
|
| 3573 |
+
))}
|
| 3574 |
+
</select>
|
| 3575 |
+
<label>Grau mínimo do teste F</label>
|
| 3576 |
+
<select value={grauF} onChange={(e) => setGrauF(Number(e.target.value))}>
|
| 3577 |
+
{GRAUS_F.map((item) => (
|
| 3578 |
+
<option key={`f-${item.value}`} value={item.value}>{item.label}</option>
|
| 3579 |
+
))}
|
| 3580 |
+
</select>
|
| 3581 |
+
<button onClick={onSearchTransform} disabled={loading}>Buscar transformações</button>
|
| 3582 |
+
</div>
|
| 3583 |
</div>
|
| 3584 |
{(selection.busca?.resultados || []).length > 0 ? (
|
| 3585 |
<div className="transform-suggestions-grid">
|
|
|
|
| 4241 |
<SectionBlock step="19" title="Exportar Modelo" subtitle="Geração do pacote .dai e download da base tratada.">
|
| 4242 |
<div className="row">
|
| 4243 |
<label>Nome do arquivo (.dai)</label>
|
| 4244 |
+
<input
|
| 4245 |
+
type="text"
|
| 4246 |
+
value={nomeArquivoExport}
|
| 4247 |
+
onChange={(e) => setNomeArquivoExport(e.target.value)}
|
| 4248 |
+
placeholder="Digite o nome do arquivo"
|
| 4249 |
+
/>
|
| 4250 |
<label>Avaliador</label>
|
| 4251 |
<select value={avaliadorSelecionado} onChange={(e) => setAvaliadorSelecionado(e.target.value)}>
|
| 4252 |
<option value="">Manter elaborador do modelo (se houver)</option>
|
|
|
|
| 4256 |
</option>
|
| 4257 |
))}
|
| 4258 |
</select>
|
| 4259 |
+
<button onClick={onExportModel} disabled={loading || !String(nomeArquivoExport || '').trim()}>Exportar modelo</button>
|
| 4260 |
<button onClick={onExportBase} disabled={loading}>Exportar base CSV</button>
|
| 4261 |
</div>
|
| 4262 |
</SectionBlock>
|
frontend/src/components/PesquisaTab.jsx
CHANGED
|
@@ -11,6 +11,7 @@ const EMPTY_FILTERS = {
|
|
| 11 |
dataMin: '',
|
| 12 |
dataMax: '',
|
| 13 |
avalFinalidade: '',
|
|
|
|
| 14 |
avalBairro: '',
|
| 15 |
avalArea: '',
|
| 16 |
avalRh: '',
|
|
@@ -140,6 +141,7 @@ function buildApiFilters(filters) {
|
|
| 140 |
tipo_modelo: filters.tipoModelo,
|
| 141 |
negociacao_modelo: filters.negociacaoModelo,
|
| 142 |
aval_finalidade: filters.avalFinalidade,
|
|
|
|
| 143 |
aval_bairro: filters.avalBairro,
|
| 144 |
data_min: filters.dataMin,
|
| 145 |
data_max: filters.dataMax,
|
|
@@ -718,17 +720,30 @@ export default function PesquisaTab() {
|
|
| 718 |
<NumberFieldInput field="avalRh" value={filters.avalRh} onChange={onFieldChange} placeholder="0" />
|
| 719 |
</label>
|
| 720 |
</div>
|
| 721 |
-
<
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 732 |
</div>
|
| 733 |
</div>
|
| 734 |
|
|
|
|
| 11 |
dataMin: '',
|
| 12 |
dataMax: '',
|
| 13 |
avalFinalidade: '',
|
| 14 |
+
avalZona: '',
|
| 15 |
avalBairro: '',
|
| 16 |
avalArea: '',
|
| 17 |
avalRh: '',
|
|
|
|
| 141 |
tipo_modelo: filters.tipoModelo,
|
| 142 |
negociacao_modelo: filters.negociacaoModelo,
|
| 143 |
aval_finalidade: filters.avalFinalidade,
|
| 144 |
+
aval_zona: filters.avalZona,
|
| 145 |
aval_bairro: filters.avalBairro,
|
| 146 |
data_min: filters.dataMin,
|
| 147 |
data_max: filters.dataMax,
|
|
|
|
| 720 |
<NumberFieldInput field="avalRh" value={filters.avalRh} onChange={onFieldChange} placeholder="0" />
|
| 721 |
</label>
|
| 722 |
</div>
|
| 723 |
+
<div className="pesquisa-bairro-zona-grid">
|
| 724 |
+
<label className="pesquisa-field">
|
| 725 |
+
Zona de avaliacao
|
| 726 |
+
<ChipAutocompleteInput
|
| 727 |
+
field="avalZona"
|
| 728 |
+
value={filters.avalZona}
|
| 729 |
+
onChange={onFieldChange}
|
| 730 |
+
placeholder="Selecione uma ou mais zonas"
|
| 731 |
+
suggestions={sugestoes.zonas_avaliacao || []}
|
| 732 |
+
panelTitle="Zonas sugeridas"
|
| 733 |
+
/>
|
| 734 |
+
</label>
|
| 735 |
+
<label className="pesquisa-field pesquisa-bairro-bottom-field">
|
| 736 |
+
Bairro do imovel
|
| 737 |
+
<ChipAutocompleteInput
|
| 738 |
+
field="avalBairro"
|
| 739 |
+
value={filters.avalBairro}
|
| 740 |
+
onChange={onFieldChange}
|
| 741 |
+
placeholder="Digite e pressione Enter"
|
| 742 |
+
suggestions={sugestoes.bairros || []}
|
| 743 |
+
panelTitle="Bairros sugeridos"
|
| 744 |
+
/>
|
| 745 |
+
</label>
|
| 746 |
+
</div>
|
| 747 |
</div>
|
| 748 |
</div>
|
| 749 |
|
frontend/src/components/SinglePillAutocomplete.jsx
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
| 2 |
+
|
| 3 |
+
function normalizeSearchText(value) {
|
| 4 |
+
return String(value || '')
|
| 5 |
+
.normalize('NFD')
|
| 6 |
+
.replace(/[\u0300-\u036f]/g, '')
|
| 7 |
+
.toLowerCase()
|
| 8 |
+
.trim()
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
function normalizeOption(option) {
|
| 12 |
+
if (typeof option === 'string') {
|
| 13 |
+
const text = String(option || '').trim()
|
| 14 |
+
return text ? { value: text, label: text, secondary: '' } : null
|
| 15 |
+
}
|
| 16 |
+
if (!option || typeof option !== 'object') return null
|
| 17 |
+
const rawValue = option.value ?? option.id ?? option.key ?? ''
|
| 18 |
+
const value = String(rawValue || '').trim()
|
| 19 |
+
if (!value) return null
|
| 20 |
+
const label = String(option.label ?? option.nome_modelo ?? option.arquivo ?? value).trim() || value
|
| 21 |
+
const secondary = String(option.secondary ?? option.arquivo ?? '').trim()
|
| 22 |
+
return { value, label, secondary }
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
export default function SinglePillAutocomplete({
|
| 26 |
+
value,
|
| 27 |
+
onChange,
|
| 28 |
+
options = [],
|
| 29 |
+
placeholder = 'Selecione um item',
|
| 30 |
+
panelTitle = '',
|
| 31 |
+
emptyMessage = 'Nenhuma sugestao encontrada.',
|
| 32 |
+
loading = false,
|
| 33 |
+
disabled = false,
|
| 34 |
+
}) {
|
| 35 |
+
const rootRef = useRef(null)
|
| 36 |
+
const inputRef = useRef(null)
|
| 37 |
+
const [query, setQuery] = useState('')
|
| 38 |
+
const [open, setOpen] = useState(false)
|
| 39 |
+
const [activeIndex, setActiveIndex] = useState(-1)
|
| 40 |
+
|
| 41 |
+
const selectedValue = String(value || '')
|
| 42 |
+
const normalizedOptions = useMemo(() => {
|
| 43 |
+
const unique = []
|
| 44 |
+
const seen = new Set()
|
| 45 |
+
;(options || []).forEach((item) => {
|
| 46 |
+
const normalized = normalizeOption(item)
|
| 47 |
+
if (!normalized) return
|
| 48 |
+
if (seen.has(normalized.value)) return
|
| 49 |
+
seen.add(normalized.value)
|
| 50 |
+
unique.push(normalized)
|
| 51 |
+
})
|
| 52 |
+
return unique
|
| 53 |
+
}, [options])
|
| 54 |
+
|
| 55 |
+
const selectedOption = useMemo(
|
| 56 |
+
() => normalizedOptions.find((item) => item.value === selectedValue) || null,
|
| 57 |
+
[normalizedOptions, selectedValue],
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
const queryNormalized = normalizeSearchText(query)
|
| 61 |
+
const filteredOptions = useMemo(() => {
|
| 62 |
+
if (loading) return []
|
| 63 |
+
if (!queryNormalized) return normalizedOptions.slice(0, 160)
|
| 64 |
+
return normalizedOptions
|
| 65 |
+
.filter((item) => (
|
| 66 |
+
normalizeSearchText(item.label).includes(queryNormalized)
|
| 67 |
+
|| normalizeSearchText(item.secondary).includes(queryNormalized)
|
| 68 |
+
))
|
| 69 |
+
.slice(0, 160)
|
| 70 |
+
}, [loading, normalizedOptions, queryNormalized])
|
| 71 |
+
|
| 72 |
+
useEffect(() => {
|
| 73 |
+
if (!open) return undefined
|
| 74 |
+
function onDocumentMouseDown(event) {
|
| 75 |
+
if (!rootRef.current) return
|
| 76 |
+
if (!rootRef.current.contains(event.target)) setOpen(false)
|
| 77 |
+
}
|
| 78 |
+
document.addEventListener('mousedown', onDocumentMouseDown)
|
| 79 |
+
return () => document.removeEventListener('mousedown', onDocumentMouseDown)
|
| 80 |
+
}, [open])
|
| 81 |
+
|
| 82 |
+
useEffect(() => {
|
| 83 |
+
if (!open || filteredOptions.length === 0) {
|
| 84 |
+
setActiveIndex(-1)
|
| 85 |
+
return
|
| 86 |
+
}
|
| 87 |
+
if (activeIndex >= filteredOptions.length) setActiveIndex(filteredOptions.length - 1)
|
| 88 |
+
}, [activeIndex, filteredOptions, open])
|
| 89 |
+
|
| 90 |
+
function emitChange(nextValue) {
|
| 91 |
+
if (typeof onChange === 'function') onChange(String(nextValue || ''))
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
function selectOption(option) {
|
| 95 |
+
if (!option) return
|
| 96 |
+
emitChange(option.value)
|
| 97 |
+
setQuery('')
|
| 98 |
+
setOpen(false)
|
| 99 |
+
setActiveIndex(-1)
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
function clearSelection(event) {
|
| 103 |
+
event.preventDefault()
|
| 104 |
+
event.stopPropagation()
|
| 105 |
+
emitChange('')
|
| 106 |
+
setQuery('')
|
| 107 |
+
setOpen(true)
|
| 108 |
+
setActiveIndex(-1)
|
| 109 |
+
window.requestAnimationFrame(() => inputRef.current?.focus())
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
function onInputChange(event) {
|
| 113 |
+
if (disabled) return
|
| 114 |
+
setQuery(event.target.value)
|
| 115 |
+
setOpen(true)
|
| 116 |
+
setActiveIndex(-1)
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
function onInputFocus() {
|
| 120 |
+
if (disabled) return
|
| 121 |
+
setOpen(true)
|
| 122 |
+
setActiveIndex(-1)
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
function onInputKeyDown(event) {
|
| 126 |
+
if (disabled) return
|
| 127 |
+
|
| 128 |
+
if (event.key === 'Escape') {
|
| 129 |
+
setOpen(false)
|
| 130 |
+
return
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
if (event.key === 'Backspace' && !query && selectedOption) {
|
| 134 |
+
emitChange('')
|
| 135 |
+
setOpen(true)
|
| 136 |
+
return
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
if (!filteredOptions.length) return
|
| 140 |
+
|
| 141 |
+
if (event.key === 'ArrowDown') {
|
| 142 |
+
event.preventDefault()
|
| 143 |
+
setOpen(true)
|
| 144 |
+
setActiveIndex((prev) => (prev < 0 ? 0 : (prev + 1) % filteredOptions.length))
|
| 145 |
+
return
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
if (event.key === 'ArrowUp') {
|
| 149 |
+
event.preventDefault()
|
| 150 |
+
setOpen(true)
|
| 151 |
+
setActiveIndex((prev) => {
|
| 152 |
+
if (prev < 0) return filteredOptions.length - 1
|
| 153 |
+
return (prev - 1 + filteredOptions.length) % filteredOptions.length
|
| 154 |
+
})
|
| 155 |
+
return
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
if (event.key === 'Enter') {
|
| 159 |
+
event.preventDefault()
|
| 160 |
+
if (activeIndex >= 0 && activeIndex < filteredOptions.length) {
|
| 161 |
+
selectOption(filteredOptions[activeIndex])
|
| 162 |
+
return
|
| 163 |
+
}
|
| 164 |
+
if (filteredOptions.length === 1) {
|
| 165 |
+
selectOption(filteredOptions[0])
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
return (
|
| 171 |
+
<div className={`chip-autocomplete chip-autocomplete-single${open ? ' is-open' : ''}${disabled ? ' is-disabled' : ''}`} ref={rootRef}>
|
| 172 |
+
<div className={`chip-autocomplete-single-control${disabled ? ' is-disabled' : ''}`}>
|
| 173 |
+
{selectedOption ? (
|
| 174 |
+
<span className="chip-autocomplete-selected chip-autocomplete-selected-inline">
|
| 175 |
+
<span>{selectedOption.label}</span>
|
| 176 |
+
{!disabled ? (
|
| 177 |
+
<button
|
| 178 |
+
type="button"
|
| 179 |
+
className="chip-autocomplete-selected-remove"
|
| 180 |
+
onMouseDown={clearSelection}
|
| 181 |
+
onClick={(event) => {
|
| 182 |
+
event.preventDefault()
|
| 183 |
+
event.stopPropagation()
|
| 184 |
+
}}
|
| 185 |
+
aria-label={`Remover ${selectedOption.label}`}
|
| 186 |
+
>
|
| 187 |
+
×
|
| 188 |
+
</button>
|
| 189 |
+
) : null}
|
| 190 |
+
</span>
|
| 191 |
+
) : null}
|
| 192 |
+
<input
|
| 193 |
+
ref={inputRef}
|
| 194 |
+
type="text"
|
| 195 |
+
className="chip-autocomplete-single-input"
|
| 196 |
+
value={query}
|
| 197 |
+
onChange={onInputChange}
|
| 198 |
+
onFocus={onInputFocus}
|
| 199 |
+
onKeyDown={onInputKeyDown}
|
| 200 |
+
placeholder={selectedOption ? '' : placeholder}
|
| 201 |
+
autoComplete="off"
|
| 202 |
+
autoCorrect="off"
|
| 203 |
+
autoCapitalize="none"
|
| 204 |
+
spellCheck={false}
|
| 205 |
+
disabled={disabled}
|
| 206 |
+
/>
|
| 207 |
+
</div>
|
| 208 |
+
|
| 209 |
+
{open && !disabled ? (
|
| 210 |
+
<div className="chip-autocomplete-panel" role="listbox">
|
| 211 |
+
{panelTitle ? <div className="chip-autocomplete-panel-head">{panelTitle}</div> : null}
|
| 212 |
+
{loading ? (
|
| 213 |
+
<div className="chip-autocomplete-empty">Carregando lista...</div>
|
| 214 |
+
) : filteredOptions.length ? (
|
| 215 |
+
<div className="chip-autocomplete-chip-wrap">
|
| 216 |
+
{filteredOptions.map((item, idx) => (
|
| 217 |
+
<button
|
| 218 |
+
type="button"
|
| 219 |
+
key={`single-chip-${item.value}-${idx}`}
|
| 220 |
+
className={`chip-autocomplete-chip${idx === activeIndex ? ' is-active' : ''}`}
|
| 221 |
+
onMouseDown={(event) => {
|
| 222 |
+
event.preventDefault()
|
| 223 |
+
event.stopPropagation()
|
| 224 |
+
selectOption(item)
|
| 225 |
+
}}
|
| 226 |
+
title={item.secondary ? `${item.label} | ${item.secondary}` : item.label}
|
| 227 |
+
>
|
| 228 |
+
{item.label}
|
| 229 |
+
</button>
|
| 230 |
+
))}
|
| 231 |
+
</div>
|
| 232 |
+
) : (
|
| 233 |
+
<div className="chip-autocomplete-empty">
|
| 234 |
+
{emptyMessage}
|
| 235 |
+
</div>
|
| 236 |
+
)}
|
| 237 |
+
</div>
|
| 238 |
+
) : null}
|
| 239 |
+
</div>
|
| 240 |
+
)
|
| 241 |
+
}
|
frontend/src/components/VisualizacaoTab.jsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import React, { useEffect, useRef, useState } from 'react'
|
| 2 |
import { api, downloadBlob } from '../api'
|
| 3 |
import DataTable from './DataTable'
|
| 4 |
import EquationFormatsPanel from './EquationFormatsPanel'
|
|
@@ -6,6 +6,7 @@ import LoadingOverlay from './LoadingOverlay'
|
|
| 6 |
import MapFrame from './MapFrame'
|
| 7 |
import PlotFigure from './PlotFigure'
|
| 8 |
import SectionBlock from './SectionBlock'
|
|
|
|
| 9 |
|
| 10 |
const INNER_TABS = [
|
| 11 |
{ key: 'mapa', label: 'Mapa' },
|
|
@@ -65,6 +66,14 @@ export default function VisualizacaoTab({ sessionId }) {
|
|
| 65 |
const deleteConfirmTimersRef = useRef({})
|
| 66 |
const uploadInputRef = useRef(null)
|
| 67 |
const temAvaliacoes = Array.isArray(baseChoices) && baseChoices.length > 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
function resetConteudoVisualizacao() {
|
| 70 |
setDados(null)
|
|
@@ -424,21 +433,18 @@ export default function VisualizacaoTab({ sessionId }) {
|
|
| 424 |
|
| 425 |
{modeloLoadSource === 'repo' ? (
|
| 426 |
<div className="row upload-repo-row">
|
| 427 |
-
<label
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
{
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
</option>
|
| 440 |
-
))}
|
| 441 |
-
</select>
|
| 442 |
<div className="row compact upload-repo-actions">
|
| 443 |
<button type="button" onClick={onCarregarModeloRepositorio} disabled={loading || repoModelosLoading || !repoModeloSelecionado}>
|
| 444 |
Carregar do repositório
|
|
|
|
| 1 |
+
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
| 2 |
import { api, downloadBlob } from '../api'
|
| 3 |
import DataTable from './DataTable'
|
| 4 |
import EquationFormatsPanel from './EquationFormatsPanel'
|
|
|
|
| 6 |
import MapFrame from './MapFrame'
|
| 7 |
import PlotFigure from './PlotFigure'
|
| 8 |
import SectionBlock from './SectionBlock'
|
| 9 |
+
import SinglePillAutocomplete from './SinglePillAutocomplete'
|
| 10 |
|
| 11 |
const INNER_TABS = [
|
| 12 |
{ key: 'mapa', label: 'Mapa' },
|
|
|
|
| 66 |
const deleteConfirmTimersRef = useRef({})
|
| 67 |
const uploadInputRef = useRef(null)
|
| 68 |
const temAvaliacoes = Array.isArray(baseChoices) && baseChoices.length > 0
|
| 69 |
+
const repoModeloOptions = useMemo(
|
| 70 |
+
() => (repoModelos || []).map((item) => ({
|
| 71 |
+
value: String(item?.id || ''),
|
| 72 |
+
label: String(item?.nome_modelo || item?.arquivo || item?.id || ''),
|
| 73 |
+
secondary: String(item?.arquivo || ''),
|
| 74 |
+
})).filter((item) => item.value && item.label),
|
| 75 |
+
[repoModelos],
|
| 76 |
+
)
|
| 77 |
|
| 78 |
function resetConteudoVisualizacao() {
|
| 79 |
setDados(null)
|
|
|
|
| 433 |
|
| 434 |
{modeloLoadSource === 'repo' ? (
|
| 435 |
<div className="row upload-repo-row">
|
| 436 |
+
<label className="upload-repo-field">
|
| 437 |
+
Modelo do repositório
|
| 438 |
+
<SinglePillAutocomplete
|
| 439 |
+
value={repoModeloSelecionado}
|
| 440 |
+
onChange={setRepoModeloSelecionado}
|
| 441 |
+
options={repoModeloOptions}
|
| 442 |
+
placeholder={repoModelosLoading ? 'Carregando lista...' : repoModeloOptions.length > 0 ? 'Digite para buscar modelo' : 'Nenhum modelo disponível'}
|
| 443 |
+
emptyMessage={repoModeloOptions.length > 0 ? 'Nenhum modelo encontrado.' : 'Nenhum modelo disponível.'}
|
| 444 |
+
loading={repoModelosLoading}
|
| 445 |
+
disabled={loading || repoModelosLoading || repoModeloOptions.length === 0}
|
| 446 |
+
/>
|
| 447 |
+
</label>
|
|
|
|
|
|
|
|
|
|
| 448 |
<div className="row compact upload-repo-actions">
|
| 449 |
<button type="button" onClick={onCarregarModeloRepositorio} disabled={loading || repoModelosLoading || !repoModeloSelecionado}>
|
| 450 |
Carregar do repositório
|
frontend/src/styles.css
CHANGED
|
@@ -951,6 +951,51 @@ button.pesquisa-otica-btn.active:hover {
|
|
| 951 |
position: relative;
|
| 952 |
}
|
| 953 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 954 |
.chip-autocomplete-selected-wrap {
|
| 955 |
display: flex;
|
| 956 |
flex-wrap: wrap;
|
|
@@ -1016,6 +1061,15 @@ button.pesquisa-otica-btn.active:hover {
|
|
| 1016 |
overflow: auto;
|
| 1017 |
}
|
| 1018 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1019 |
.chip-autocomplete-panel-head {
|
| 1020 |
font-size: 0.72rem;
|
| 1021 |
font-weight: 700;
|
|
@@ -1155,14 +1209,23 @@ button.pesquisa-otica-btn.active:hover {
|
|
| 1155 |
|
| 1156 |
.pesquisa-avaliando-periodo-pair {
|
| 1157 |
margin: 0;
|
|
|
|
|
|
|
| 1158 |
}
|
| 1159 |
|
| 1160 |
.pesquisa-avaliando-bottom-stack {
|
| 1161 |
gap: 12px;
|
| 1162 |
}
|
| 1163 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1164 |
.pesquisa-bairro-bottom-field {
|
| 1165 |
-
|
| 1166 |
}
|
| 1167 |
|
| 1168 |
.pesquisa-avaliando-bottom-grid .pesquisa-field-pair {
|
|
@@ -1985,6 +2048,14 @@ button.model-source-back-btn {
|
|
| 1985 |
|
| 1986 |
.upload-repo-row {
|
| 1987 |
margin-bottom: 10px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1988 |
}
|
| 1989 |
|
| 1990 |
.upload-repo-actions {
|
|
@@ -2622,6 +2693,43 @@ button.btn-upload-select {
|
|
| 2622 |
margin-bottom: 18px;
|
| 2623 |
}
|
| 2624 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2625 |
.transform-preview-summary {
|
| 2626 |
border: 1px solid #d8e5f2;
|
| 2627 |
border-radius: 12px;
|
|
@@ -3998,6 +4106,10 @@ button.btn-download-subtle {
|
|
| 3998 |
grid-template-columns: 1fr;
|
| 3999 |
}
|
| 4000 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4001 |
.pesquisa-dynamic-filter-row,
|
| 4002 |
.pesquisa-range-values-row,
|
| 4003 |
.pesquisa-range-row,
|
|
|
|
| 951 |
position: relative;
|
| 952 |
}
|
| 953 |
|
| 954 |
+
.chip-autocomplete-single-control {
|
| 955 |
+
display: flex;
|
| 956 |
+
align-items: center;
|
| 957 |
+
gap: 6px;
|
| 958 |
+
flex-wrap: wrap;
|
| 959 |
+
min-height: 34px;
|
| 960 |
+
width: 100%;
|
| 961 |
+
border: 1px solid #c8d7e6;
|
| 962 |
+
border-radius: 10px;
|
| 963 |
+
background: #ffffff;
|
| 964 |
+
padding: 4px 8px;
|
| 965 |
+
}
|
| 966 |
+
|
| 967 |
+
.chip-autocomplete-single-control:focus-within {
|
| 968 |
+
border-color: #6aa1d4;
|
| 969 |
+
box-shadow: 0 0 0 2px rgba(106, 161, 212, 0.16);
|
| 970 |
+
}
|
| 971 |
+
|
| 972 |
+
.chip-autocomplete-single-control.is-disabled {
|
| 973 |
+
background: #f6f8fb;
|
| 974 |
+
color: #74889c;
|
| 975 |
+
cursor: not-allowed;
|
| 976 |
+
}
|
| 977 |
+
|
| 978 |
+
.chip-autocomplete-single-input {
|
| 979 |
+
flex: 1 1 120px;
|
| 980 |
+
min-width: 120px;
|
| 981 |
+
border: none;
|
| 982 |
+
background: transparent;
|
| 983 |
+
color: #2e4760;
|
| 984 |
+
font-size: 0.84rem;
|
| 985 |
+
line-height: 1.25;
|
| 986 |
+
padding: 4px 2px;
|
| 987 |
+
min-height: 22px;
|
| 988 |
+
}
|
| 989 |
+
|
| 990 |
+
.chip-autocomplete-single-input:focus {
|
| 991 |
+
outline: none;
|
| 992 |
+
box-shadow: none;
|
| 993 |
+
}
|
| 994 |
+
|
| 995 |
+
.chip-autocomplete-single .chip-autocomplete-selected-inline {
|
| 996 |
+
margin: 0;
|
| 997 |
+
}
|
| 998 |
+
|
| 999 |
.chip-autocomplete-selected-wrap {
|
| 1000 |
display: flex;
|
| 1001 |
flex-wrap: wrap;
|
|
|
|
| 1061 |
overflow: auto;
|
| 1062 |
}
|
| 1063 |
|
| 1064 |
+
.workflow-section[data-section-step="1"] .chip-autocomplete.is-open .chip-autocomplete-panel {
|
| 1065 |
+
position: static;
|
| 1066 |
+
top: auto;
|
| 1067 |
+
left: auto;
|
| 1068 |
+
right: auto;
|
| 1069 |
+
margin-top: 6px;
|
| 1070 |
+
z-index: auto;
|
| 1071 |
+
}
|
| 1072 |
+
|
| 1073 |
.chip-autocomplete-panel-head {
|
| 1074 |
font-size: 0.72rem;
|
| 1075 |
font-weight: 700;
|
|
|
|
| 1209 |
|
| 1210 |
.pesquisa-avaliando-periodo-pair {
|
| 1211 |
margin: 0;
|
| 1212 |
+
height: auto;
|
| 1213 |
+
align-self: start;
|
| 1214 |
}
|
| 1215 |
|
| 1216 |
.pesquisa-avaliando-bottom-stack {
|
| 1217 |
gap: 12px;
|
| 1218 |
}
|
| 1219 |
|
| 1220 |
+
.pesquisa-bairro-zona-grid {
|
| 1221 |
+
display: grid;
|
| 1222 |
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
| 1223 |
+
gap: 12px 14px;
|
| 1224 |
+
align-items: start;
|
| 1225 |
+
}
|
| 1226 |
+
|
| 1227 |
.pesquisa-bairro-bottom-field {
|
| 1228 |
+
min-width: 0;
|
| 1229 |
}
|
| 1230 |
|
| 1231 |
.pesquisa-avaliando-bottom-grid .pesquisa-field-pair {
|
|
|
|
| 2048 |
|
| 2049 |
.upload-repo-row {
|
| 2050 |
margin-bottom: 10px;
|
| 2051 |
+
align-items: flex-start;
|
| 2052 |
+
}
|
| 2053 |
+
|
| 2054 |
+
.upload-repo-field {
|
| 2055 |
+
display: grid;
|
| 2056 |
+
gap: 7px;
|
| 2057 |
+
flex: 1 1 420px;
|
| 2058 |
+
min-width: min(100%, 300px);
|
| 2059 |
}
|
| 2060 |
|
| 2061 |
.upload-repo-actions {
|
|
|
|
| 2693 |
margin-bottom: 18px;
|
| 2694 |
}
|
| 2695 |
|
| 2696 |
+
.section11-toggle-wrap {
|
| 2697 |
+
margin-bottom: 12px;
|
| 2698 |
+
display: flex;
|
| 2699 |
+
align-items: center;
|
| 2700 |
+
flex-wrap: wrap;
|
| 2701 |
+
gap: 8px;
|
| 2702 |
+
}
|
| 2703 |
+
|
| 2704 |
+
.btn-section11-unlock {
|
| 2705 |
+
--btn-bg-start: #f4f8fc;
|
| 2706 |
+
--btn-bg-end: #e8eff6;
|
| 2707 |
+
--btn-border: #c4d1df;
|
| 2708 |
+
--btn-shadow-soft: rgba(75, 102, 128, 0.12);
|
| 2709 |
+
--btn-shadow-strong: rgba(75, 102, 128, 0.2);
|
| 2710 |
+
color: #395470;
|
| 2711 |
+
}
|
| 2712 |
+
|
| 2713 |
+
.section11-search-criteria {
|
| 2714 |
+
margin-top: 8px;
|
| 2715 |
+
padding-top: 11px;
|
| 2716 |
+
border-top: 1px solid #d7e2ee;
|
| 2717 |
+
display: grid;
|
| 2718 |
+
gap: 8px;
|
| 2719 |
+
}
|
| 2720 |
+
|
| 2721 |
+
.section11-search-criteria-title {
|
| 2722 |
+
font-size: 0.76rem;
|
| 2723 |
+
font-weight: 800;
|
| 2724 |
+
letter-spacing: 0.03em;
|
| 2725 |
+
text-transform: uppercase;
|
| 2726 |
+
color: #4b6177;
|
| 2727 |
+
}
|
| 2728 |
+
|
| 2729 |
+
.section11-search-row {
|
| 2730 |
+
margin-bottom: 0;
|
| 2731 |
+
}
|
| 2732 |
+
|
| 2733 |
.transform-preview-summary {
|
| 2734 |
border: 1px solid #d8e5f2;
|
| 2735 |
border-radius: 12px;
|
|
|
|
| 4106 |
grid-template-columns: 1fr;
|
| 4107 |
}
|
| 4108 |
|
| 4109 |
+
.pesquisa-bairro-zona-grid {
|
| 4110 |
+
grid-template-columns: 1fr;
|
| 4111 |
+
}
|
| 4112 |
+
|
| 4113 |
.pesquisa-dynamic-filter-row,
|
| 4114 |
.pesquisa-range-values-row,
|
| 4115 |
.pesquisa-range-row,
|