Spaces:
Running
Running
Update salas_2026.py
Browse files- salas_2026.py +123 -129
salas_2026.py
CHANGED
|
@@ -1,154 +1,148 @@
|
|
| 1 |
-
import openpyxl
|
| 2 |
-
import xgboost as xgb
|
| 3 |
import gradio as gr
|
| 4 |
-
import joblib
|
| 5 |
import pandas as pd
|
| 6 |
import numpy as np
|
| 7 |
-
import
|
| 8 |
-
import
|
| 9 |
-
|
| 10 |
-
import
|
| 11 |
-
from sklearn.preprocessing import MinMaxScaler
|
| 12 |
-
from reportlab.lib import colors
|
| 13 |
-
from reportlab.lib.units import inch
|
| 14 |
-
from reportlab.lib.pagesizes import A4, landscape
|
| 15 |
from reportlab.lib.styles import getSampleStyleSheet
|
| 16 |
-
from reportlab.pdfgen.canvas import Canvas
|
| 17 |
-
from reportlab.platypus import SimpleDocTemplate, Spacer, Image, Frame, PageTemplate, BaseDocTemplate, Table, Paragraph, NextPageTemplate, PageBreak
|
| 18 |
-
|
| 19 |
-
# Atualização dos argumentos para incluir B e o novo range de anos
|
| 20 |
-
def get_report(FON, X, Y, RH, ANO_C, ATOTAL, B, C, D, E, ANO_2020, ANO_2021, ANO_2022, ANO_2023, ANO_2024, ANO_2025, pred):
|
| 21 |
-
# Define the page size
|
| 22 |
-
pagesize = A4
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
def on_page(canvas, doc):
|
| 25 |
-
|
| 26 |
-
canvas.
|
| 27 |
-
|
| 28 |
-
def on_page_landscape(canvas, doc):
|
| 29 |
-
return on_page(canvas, doc, pagesize=landscape(A4))
|
| 30 |
-
|
| 31 |
-
padding = dict(
|
| 32 |
-
leftPadding=72,
|
| 33 |
-
rightPadding=72,
|
| 34 |
-
topPadding=72,
|
| 35 |
-
bottomPadding=18)
|
| 36 |
-
|
| 37 |
-
portrait_frame = Frame(0, 0, *A4, **padding)
|
| 38 |
-
landscape_frame = Frame(0, 0, *landscape(A4), **padding)
|
| 39 |
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
onPage=on_page,
|
| 43 |
-
pagesize=A4)
|
| 44 |
-
|
| 45 |
-
landscape_template = PageTemplate(id='landscape',
|
| 46 |
-
frames=landscape_frame,
|
| 47 |
-
onPage=on_page_landscape,
|
| 48 |
-
pagesize=landscape(A4))
|
| 49 |
-
|
| 50 |
-
doc = BaseDocTemplate('report.pdf',
|
| 51 |
-
pageTemplates=[portrait_template,landscape_template])
|
| 52 |
-
|
| 53 |
styles = getSampleStyleSheet()
|
|
|
|
|
|
|
|
|
|
| 54 |
|
|
|
|
| 55 |
story = [
|
| 56 |
-
|
| 57 |
-
Spacer(1,
|
| 58 |
-
Paragraph(
|
| 59 |
-
Paragraph(
|
| 60 |
-
Paragraph(f"
|
| 61 |
-
|
| 62 |
-
Paragraph(f"
|
| 63 |
-
Paragraph(f"
|
| 64 |
-
|
| 65 |
-
Paragraph(
|
| 66 |
-
Paragraph(f"Padrão Construtivo B: {'Sim' if B else 'Não'}", styles['Normal']),
|
| 67 |
-
Paragraph(f"Padrão Construtivo C: {'Sim' if C else 'Não'}", styles['Normal']),
|
| 68 |
-
Paragraph(f"Padrão Construtivo D: {'Sim' if D else 'Não'}", styles['Normal']),
|
| 69 |
-
Paragraph(f"Padrão Construtivo E: {'Sim' if E else 'Não'}", styles['Normal']),
|
| 70 |
-
Paragraph(f"Ano 2020 - Exercício Fiscal 2021: {'Sim' if ANO_2020 else 'Não'}", styles['Normal']),
|
| 71 |
-
Paragraph(f"Ano 2021 - Exercício Fiscal 2022: {'Sim' if ANO_2021 else 'Não'}", styles['Normal']),
|
| 72 |
-
Paragraph(f"Ano 2022 - Exercício Fiscal 2023: {'Sim' if ANO_2022 else 'Não'}", styles['Normal']),
|
| 73 |
-
Paragraph(f"Ano 2023 - Exercício Fiscal 2024: {'Sim' if ANO_2023 else 'Não'}", styles['Normal']),
|
| 74 |
-
Paragraph(f"Ano 2024 - Exercício Fiscal 2025: {'Sim' if ANO_2024 else 'Não'}", styles['Normal']),
|
| 75 |
-
Paragraph(f"Ano 2025 - Exercício Fiscal 2026: {'Sim' if ANO_2025 else 'Não'}", styles['Normal']),
|
| 76 |
-
Spacer(1, 36),
|
| 77 |
-
Paragraph('Resultado', styles['Heading1']),
|
| 78 |
-
Paragraph('Valor do imóvel (R$)', styles['Heading2']),
|
| 79 |
-
Paragraph(f"R$ {round(pred[0][0] * ATOTAL, -2)}", styles['Normal']),
|
| 80 |
]
|
| 81 |
-
|
| 82 |
doc.build(story)
|
| 83 |
-
|
| 84 |
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
'ANO_C': [ANO_C],
|
| 92 |
-
'ATOTAL': np.log(ATOTAL),
|
| 93 |
-
'B': 1 if B else 0,
|
| 94 |
-
'C': 1 if C else 0,
|
| 95 |
-
'D': 1 if D else 0,
|
| 96 |
-
'E': 1 if E else 0,
|
| 97 |
-
'ANO_2020': 1 if ANO_2020 else 0,
|
| 98 |
-
'ANO_2021': 1 if ANO_2021 else 0,
|
| 99 |
-
'ANO_2022': 1 if ANO_2022 else 0,
|
| 100 |
-
'ANO_2023': 1 if ANO_2023 else 0,
|
| 101 |
-
'ANO_2024': 1 if ANO_2024 else 0,
|
| 102 |
-
'ANO_2025': 1 if ANO_2025 else 0,
|
| 103 |
-
})
|
| 104 |
-
|
| 105 |
-
input_scaler = joblib.load("dados/salas/input_scaler_salas_2026.save")
|
| 106 |
-
df = input_scaler.transform(df)
|
| 107 |
|
| 108 |
-
#
|
| 109 |
-
|
| 110 |
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
pred = loaded_model.predict(df)
|
| 116 |
-
output_scaler = joblib.load("dados/salas/output_scaler_salas_2026.save")
|
| 117 |
-
pred = output_scaler.inverse_transform(np.array(pred).reshape(-1,1))
|
| 118 |
-
pred = np.exp(pred)
|
| 119 |
-
|
| 120 |
-
get_report(FON, X, Y, RH, ANO_C, ATOTAL, B, C, D, E, ANO_2020, ANO_2021, ANO_2022, ANO_2023, ANO_2024, ANO_2025, pred)
|
| 121 |
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
X = gr.Number(label='Coordenada X (TM-POA)', value=281554.)
|
| 127 |
-
Y = gr.Number(label='Coordenada Y (TM-POA)', value=1675418.)
|
| 128 |
-
RH = gr.Number(value=120, label='Região Homogênea')
|
| 129 |
-
ANO_C = gr.Number(value=2020, label='Ano de Construção (Conforme Cadastro Imobiliário)')
|
| 130 |
-
ATOTAL = gr.Number(label='Área Total (Conforme Cadastro Imobiliário)', value=60.)
|
| 131 |
|
| 132 |
-
#
|
| 133 |
-
|
| 134 |
-
C = gr.Checkbox(label='Padrão Construtivo C', value=True)
|
| 135 |
-
D = gr.Checkbox(label='Padrão Construtivo D', value=False)
|
| 136 |
-
E = gr.Checkbox(label='Padrão Construtivo E', value=False)
|
| 137 |
|
| 138 |
-
#
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
|
| 148 |
-
title = '
|
| 149 |
|
| 150 |
description = f"""
|
| 151 |
# <p style="text-align: center;">Modelo para Salas Comerciais - XGBoost Regressor - 2026</p>
|
| 152 |
-
<p style="text-align: center;">
|
| 153 |
<hr style="color: #333; background-color: #333; height: 1px; border: none;">
|
| 154 |
"""
|
|
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
|
|
|
| 2 |
import pandas as pd
|
| 3 |
import numpy as np
|
| 4 |
+
import joblib
|
| 5 |
+
import xgboost as xgb
|
| 6 |
+
from reportlab.lib.pagesizes import A4
|
| 7 |
+
from reportlab.platypus import Spacer, Frame, PageTemplate, BaseDocTemplate, Paragraph
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
from reportlab.lib.styles import getSampleStyleSheet
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
+
# --- 1. FUNÇÃO GERADORA DE PDF ---
|
| 11 |
+
def get_report(FON, X, Y, RH, ANO_C, ATOTAL, PADRAO, ANO_REF, pred):
|
| 12 |
+
"""Gera um PDF simples com o resultado da avaliação."""
|
| 13 |
+
filename = 'PDF_Avaliacao.pdf'
|
| 14 |
+
|
| 15 |
def on_page(canvas, doc):
|
| 16 |
+
canvas.setTitle("Documento de Avaliação")
|
| 17 |
+
canvas.drawString(500, 800, "Pág. %d" % doc.page)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
+
# Configuração do Documento
|
| 20 |
+
doc = BaseDocTemplate(filename, pagesize=A4)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
styles = getSampleStyleSheet()
|
| 22 |
+
frame = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height, id='normal')
|
| 23 |
+
template = PageTemplate(id='test', frames=frame, onPage=on_page)
|
| 24 |
+
doc.addPageTemplates([template])
|
| 25 |
|
| 26 |
+
# Conteúdo
|
| 27 |
story = [
|
| 28 |
+
Paragraph("Documento de Avaliação - Modelo Híbrido 2026", styles['Title']),
|
| 29 |
+
Spacer(1, 12),
|
| 30 |
+
Paragraph(f"<b>Data de Referência:</b> {ANO_REF}", styles['Normal']),
|
| 31 |
+
Paragraph(f"<b>Características:</b> Padrão {PADRAO}, {ATOTAL} m², Construído em {ANO_C}", styles['Normal']),
|
| 32 |
+
Paragraph(f"<b>Localização:</b> X={X}, Y={Y}, RH={RH}", styles['Normal']),
|
| 33 |
+
Spacer(1, 24),
|
| 34 |
+
Paragraph(f"<b>Valor Unitário Estimado:</b> R$ {round(pred[0], 2):,.2f}/m²", styles['Heading2']),
|
| 35 |
+
Paragraph(f"<b>Valor Total Estimado:</b> R$ {round(pred[0] * float(ATOTAL), -2):,.2f}", styles['Heading2']),
|
| 36 |
+
Spacer(1, 24),
|
| 37 |
+
Paragraph("Nota: Este valor é uma estimativa estatística com grau de confiança de 80%.", styles['Italic'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
]
|
|
|
|
| 39 |
doc.build(story)
|
| 40 |
+
return filename
|
| 41 |
|
| 42 |
+
# --- 2. FUNÇÃO DE INFERÊNCIA CORRIGIDA ---
|
| 43 |
+
def execute_valuation(FON_STR, X, Y, RH, ANO_C, ATOTAL, PADRAO, ANO_REF):
|
| 44 |
+
# Mapeamento manual das colunas para garantir ordem correta (O MODELO EXIGE A e B)
|
| 45 |
+
cols_v1 = ['FON', 'X', 'Y', 'RH', 'ANO_C', 'ATOTAL',
|
| 46 |
+
'A', 'B','C', 'D', 'E',
|
| 47 |
+
'ANO_2019', 'ANO_2020', 'ANO_2021', 'ANO_2022', 'ANO_2023', 'ANO_2024']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
+
# 1. Tratamento de Inputs
|
| 50 |
+
is_oferta = 1 if FON_STR == "Oferta" else 0
|
| 51 |
|
| 52 |
+
# --- CORREÇÃO AQUI ---
|
| 53 |
+
# Precisamos criar as chaves para A, B, C, D, E para o modelo não quebrar.
|
| 54 |
+
# Inicializamos todas com 0.
|
| 55 |
+
padrões = {k: 0 for k in ['A', 'B', 'C', 'D', 'E']}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
+
# Apenas se o padrão escolhido (C, D ou E) estiver na lista, setamos 1.
|
| 58 |
+
# Como A e B nunca serão escolhidos na UI, eles ficarão sempre como 0, o que é correto.
|
| 59 |
+
if PADRAO in padrões: padrões[PADRAO] = 1
|
| 60 |
+
# ---------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
+
# Dummies de Ano V1 (2019-2024)
|
| 63 |
+
anos_v1 = {f'ANO_{a}': (1 if str(ANO_REF) == str(a) else 0) for a in range(2019, 2025)}
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
+
# Dummy de Ano V2 (2025)
|
| 66 |
+
is_2025 = 1 if str(ANO_REF) == '2025' else 0
|
| 67 |
+
|
| 68 |
+
# Montagem do Dicionário de Dados Brutos
|
| 69 |
+
data_dict = {
|
| 70 |
+
'FON': [is_oferta],
|
| 71 |
+
'X': [float(X)],
|
| 72 |
+
'Y': [float(Y)],
|
| 73 |
+
'RH': [float(RH)],
|
| 74 |
+
'ANO_C': [float(ANO_C)],
|
| 75 |
+
'ATOTAL': [np.log(float(ATOTAL))],
|
| 76 |
+
**padrões, # Agora isso expande A=0, B=0, C=..., D=..., E=...
|
| 77 |
+
**anos_v1,
|
| 78 |
+
'ANO_2025': [is_2025]
|
| 79 |
+
}
|
| 80 |
|
| 81 |
+
full_df = pd.DataFrame(data_dict)
|
| 82 |
+
|
| 83 |
+
try:
|
| 84 |
+
# --- PIPELINE DE PREDIÇÃO ---
|
| 85 |
+
|
| 86 |
+
# 1. Carregar Scalers e Modelos
|
| 87 |
+
input_scaler = joblib.load("input_scaler_salas_comerciais_2025.save")
|
| 88 |
+
output_scaler = joblib.load("output_scaler_salas_comerciais_2025.save")
|
| 89 |
+
model_v1_loaded = xgb.Booster()
|
| 90 |
+
model_v1_loaded.load_model("SALAS_COMERCIAIS_2020_a_2025.model")
|
| 91 |
+
model_v2_loaded = xgb.Booster()
|
| 92 |
+
model_v2_loaded.load_model("SALAS_COMERCIAIS_RESIDUAL_2026.model")
|
| 93 |
+
|
| 94 |
+
# 2. Preparar Input para V1
|
| 95 |
+
# Agora full_df TEM as colunas 'A' e 'B' (com valor 0), então não vai dar erro
|
| 96 |
+
df_v1_input = full_df[cols_v1].copy()
|
| 97 |
+
|
| 98 |
+
array_scaled = input_scaler.transform(df_v1_input)
|
| 99 |
+
df_v1_scaled = pd.DataFrame(array_scaled, columns=cols_v1)
|
| 100 |
+
|
| 101 |
+
# 3. Predição Base (V1)
|
| 102 |
+
base_pred = model_v1_loaded.predict(xgb.DMatrix(df_v1_scaled))
|
| 103 |
+
|
| 104 |
+
# 4. Preparar Input para V2
|
| 105 |
+
df_v2_input = df_v1_scaled.copy()
|
| 106 |
+
df_v2_input['ANO_2025'] = full_df['ANO_2025'].values
|
| 107 |
+
|
| 108 |
+
cols_v2_final = cols_v1 + ['ANO_2025']
|
| 109 |
+
dtest_v2 = xgb.DMatrix(df_v2_input[cols_v2_final])
|
| 110 |
+
|
| 111 |
+
# 5. Predição Final
|
| 112 |
+
dtest_v2.set_base_margin(base_pred)
|
| 113 |
+
final_pred_scaled = model_v2_loaded.predict(dtest_v2)
|
| 114 |
+
|
| 115 |
+
# 6. Inversão da Escala
|
| 116 |
+
pred_log = output_scaler.inverse_transform(np.array(final_pred_scaled).reshape(-1,1))
|
| 117 |
+
val_unit_real = np.exp(pred_log).flatten()[0]
|
| 118 |
+
|
| 119 |
+
# Resultados
|
| 120 |
+
total_val = val_unit_real * float(ATOTAL)
|
| 121 |
+
text_output = f"R$ {total_val:,.2f} (Total)\nR$ {val_unit_real:,.2f}/m²"
|
| 122 |
+
|
| 123 |
+
# Gerar PDF
|
| 124 |
+
pdf_path = get_report(FON_STR, X, Y, RH, ANO_C, ATOTAL, PADRAO, ANO_REF, [val_unit_real])
|
| 125 |
+
|
| 126 |
+
return text_output, pdf_path
|
| 127 |
+
|
| 128 |
+
except Exception as e:
|
| 129 |
+
return f"Erro no cálculo: {str(e)}", None
|
| 130 |
+
|
| 131 |
+
def load_inputs():
|
| 132 |
+
fon = gr.Radio(["Transação", "Oferta"], label="Fonte", value="Transação")
|
| 133 |
+
coord_x = gr.Number(label="Coordenada X (UTM)", value=281554)
|
| 134 |
+
coord_y = gr.Number(label="Coordenada Y (UTM)", value=1675418)
|
| 135 |
+
rh = gr.Slider(1, 380, label="Região Homogênea (RH)", value=120)
|
| 136 |
+
ano_c = gr.Number(label="Ano Construção", value=2020)
|
| 137 |
+
area = gr.Number(label="Área Privativa (m²)", value=60)
|
| 138 |
+
padrao = gr.Radio(["C", "D", "E"], label="Padrão Construtivo", value="C")
|
| 139 |
+
ano_ref = gr.Dropdown([str(i) for i in range(2020, 2026)], label="Ano Base da Avaliação", value="2025")
|
| 140 |
+
return [fon, coord_x, coord_y, rh, ano_c, area, padrao, ano_ref]
|
| 141 |
|
| 142 |
+
title = 'Salas Comerciais - XGBoost Regressor - 2026'
|
| 143 |
|
| 144 |
description = f"""
|
| 145 |
# <p style="text-align: center;">Modelo para Salas Comerciais - XGBoost Regressor - 2026</p>
|
| 146 |
+
<p style="text-align: center;">Dados referentes aos anos 2019 a 2025.</p>
|
| 147 |
<hr style="color: #333; background-color: #333; height: 1px; border: none;">
|
| 148 |
"""
|