Spaces:
Running
Running
| import gradio as gr | |
| import pandas as pd | |
| import numpy as np | |
| import joblib | |
| import xgboost as xgb | |
| from reportlab.lib.pagesizes import A4 | |
| from reportlab.platypus import Spacer, Frame, PageTemplate, BaseDocTemplate, Paragraph | |
| from reportlab.lib.styles import getSampleStyleSheet | |
| # --- 1. FUNÇÃO GERADORA DE PDF --- | |
| def get_report(FON, X, Y, RH, ANO_C, ATOTAL, PADRAO, ANO_REF, pred): | |
| """Gera um PDF simples com o resultado da avaliação.""" | |
| filename = 'PDF_Avaliacao.pdf' | |
| def on_page(canvas, doc): | |
| canvas.setTitle("Documento de Avaliação") | |
| canvas.drawString(500, 800, "Pág. %d" % doc.page) | |
| # Configuração do Documento | |
| doc = BaseDocTemplate(filename, pagesize=A4) | |
| styles = getSampleStyleSheet() | |
| frame = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height, id='normal') | |
| template = PageTemplate(id='test', frames=frame, onPage=on_page) | |
| doc.addPageTemplates([template]) | |
| # Conteúdo | |
| story = [ | |
| Paragraph("Salas Comerciais - XGBoost Modelo Híbrido 2026", styles['Title']), | |
| Spacer(1, 12), | |
| Paragraph(f"<b>Ano de Referência:</b> {ANO_REF}", styles['Normal']), | |
| Paragraph(f"<b>Características:</b> Padrão {PADRAO}, {ATOTAL} m², Construído em {ANO_C}", styles['Normal']), | |
| Paragraph(f"<b>Localização:</b> X={X}, Y={Y}, RH={RH}", styles['Normal']), | |
| Spacer(1, 24), | |
| Paragraph(f"<b>Valor Unitário Estimado:</b> R$ {round(pred[0], 2):,.2f}/m²", styles['Heading2']), | |
| Paragraph(f"<b>Valor Total Estimado:</b> R$ {round(pred[0] * float(ATOTAL), -2):,.2f}", styles['Heading2']), | |
| Spacer(1, 24), | |
| Paragraph("Nota: Este valor é uma estimativa estatística com grau de confiança de 80%.", styles['Italic']) | |
| ] | |
| doc.build(story) | |
| return filename | |
| # --- 2. FUNÇÃO DE INFERÊNCIA CORRIGIDA --- | |
| def execute_valuation(FON_STR, X, Y, RH, ANO_C, ATOTAL, PADRAO, ANO_REF): | |
| # Mapeamento manual das colunas para garantir ordem correta (O MODELO EXIGE A e B) | |
| cols_v1 = ['FON', 'X', 'Y', 'RH', 'ANO_C', 'ATOTAL', | |
| 'A', 'B','C', 'D', 'E', | |
| 'ANO_2019', 'ANO_2020', 'ANO_2021', 'ANO_2022', 'ANO_2023', 'ANO_2024'] | |
| # 1. Tratamento de Inputs | |
| is_oferta = 1 if FON_STR == "Oferta" else 0 | |
| # --- CORREÇÃO AQUI --- | |
| # Precisamos criar as chaves para A, B, C, D, E para o modelo não quebrar. | |
| # Inicializamos todas com 0. | |
| padrões = {k: 0 for k in ['A', 'B', 'C', 'D', 'E']} | |
| # Apenas se o padrão escolhido (C, D ou E) estiver na lista, setamos 1. | |
| # Como A e B nunca serão escolhidos na UI, eles ficarão sempre como 0, o que é correto. | |
| if PADRAO in padrões: padrões[PADRAO] = 1 | |
| # --------------------- | |
| # Dummies de Ano V1 (2019-2024) | |
| anos_v1 = {f'ANO_{a}': (1 if str(ANO_REF) == str(a) else 0) for a in range(2019, 2025)} | |
| # Dummy de Ano V2 (2025) | |
| is_2025 = 1 if str(ANO_REF) == '2025' else 0 | |
| # Montagem do Dicionário de Dados Brutos | |
| data_dict = { | |
| 'FON': [is_oferta], | |
| 'X': [float(X)], | |
| 'Y': [float(Y)], | |
| 'RH': [float(RH)], | |
| 'ANO_C': [float(ANO_C)], | |
| 'ATOTAL': [np.log(float(ATOTAL))], | |
| **padrões, # Agora isso expande A=0, B=0, C=..., D=..., E=... | |
| **anos_v1, | |
| 'ANO_2025': [is_2025] | |
| } | |
| full_df = pd.DataFrame(data_dict) | |
| try: | |
| # --- PIPELINE DE PREDIÇÃO --- | |
| # 1. Carregar Scalers e Modelos | |
| input_scaler = joblib.load("dados/salas/input_scaler_salas_comerciais_2025.save") | |
| output_scaler = joblib.load("dados/salas/output_scaler_salas_comerciais_2025.save") | |
| model_v1_loaded = xgb.Booster() | |
| model_v1_loaded.load_model("dados/salas/SALAS_COMERCIAIS_2020_a_2025.model") | |
| model_v2_loaded = xgb.Booster() | |
| model_v2_loaded.load_model("dados/salas/SALAS_COMERCIAIS_RESIDUAL_2026.model") | |
| # 2. Preparar Input para V1 | |
| # Agora full_df TEM as colunas 'A' e 'B' (com valor 0), então não vai dar erro | |
| df_v1_input = full_df[cols_v1].copy() | |
| array_scaled = input_scaler.transform(df_v1_input) | |
| df_v1_scaled = pd.DataFrame(array_scaled, columns=cols_v1) | |
| # 3. Predição Base (V1) | |
| base_pred = model_v1_loaded.predict(xgb.DMatrix(df_v1_scaled)) | |
| # 4. Preparar Input para V2 | |
| df_v2_input = df_v1_scaled.copy() | |
| df_v2_input['ANO_2025'] = full_df['ANO_2025'].values | |
| cols_v2_final = cols_v1 + ['ANO_2025'] | |
| dtest_v2 = xgb.DMatrix(df_v2_input[cols_v2_final]) | |
| # 5. Predição Final | |
| dtest_v2.set_base_margin(base_pred) | |
| final_pred_scaled = model_v2_loaded.predict(dtest_v2) | |
| # 6. Inversão da Escala | |
| pred_log = output_scaler.inverse_transform(np.array(final_pred_scaled).reshape(-1,1)) | |
| val_unit_real = np.exp(pred_log).flatten()[0] | |
| # Resultados | |
| total_val = val_unit_real * float(ATOTAL) | |
| text_output = f"R$ {total_val:,.2f} (Total)\nR$ {val_unit_real:,.2f}/m²" | |
| # Gerar PDF | |
| pdf_path = get_report(FON_STR, X, Y, RH, ANO_C, ATOTAL, PADRAO, ANO_REF, [val_unit_real]) | |
| return text_output, pdf_path | |
| except Exception as e: | |
| return f"Erro no cálculo: {str(e)}", None | |
| def load_inputs(): | |
| fon = gr.Radio(["Transação", "Oferta"], label="Fonte", value="Transação") | |
| coord_x = gr.Number(label="Coordenada X (UTM)", value=281554) | |
| coord_y = gr.Number(label="Coordenada Y (UTM)", value=1675418) | |
| rh = gr.Slider(1, 380, label="Região Homogênea (RH)", value=120) | |
| ano_c = gr.Number(label="Ano Construção", value=2020) | |
| area = gr.Number(label="Área Total (Conforme Cadastro Imobiliário)", value=60) | |
| padrao = gr.Radio(["C", "D", "E"], label="Padrão Construtivo", value="C") | |
| ano_ref = gr.Dropdown([str(i) for i in range(2019, 2026)], label="Ano Base da Avaliação", value="2025") | |
| return [fon, coord_x, coord_y, rh, ano_c, area, padrao, ano_ref] | |
| title = 'Salas Comerciais - XGBoost Regressor - 2026' | |
| description = f""" | |
| # <p style="text-align: center;">Modelo para Salas Comerciais - XGBoost Regressor - 2026</p> | |
| <p style="text-align: center;">Dados referentes aos anos 2019 a 2025.</p> | |
| <hr style="color: #333; background-color: #333; height: 1px; border: none;"> | |
| """ |