fschwartzer commited on
Commit
e3367cf
·
verified ·
1 Parent(s): 221ab38

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +479 -80
app.py CHANGED
@@ -1,102 +1,501 @@
1
  import pandas as pd
2
  import numpy as np
3
  import gradio as gr
 
 
4
 
5
- def processar_diagnostico_gradio(arquivo):
 
 
 
 
6
  if arquivo is None:
7
- return "Por favor, faça o upload de um arquivo Excel.", None
8
-
9
- try:
10
- # Carregar o dataframe
11
- df = pd.read_excel(arquivo.name)
12
-
13
- # Validação básica de colunas
14
- colunas_obrigatorias = ['data_encaminhamento', 'siat_inscricao', 'siat_logradouro',
15
- 'siat_numero', 'siat_complemento', 'valor_oferta']
16
-
17
- for col in colunas_obrigatorias:
18
- if col not in df.columns:
19
- return f"Erro: A coluna '{col}' não foi encontrada no arquivo.", None
20
-
21
- # Processamento de datas
22
- df['data_encaminhamento'] = pd.to_datetime(
23
- df['data_encaminhamento'],
24
- format="%d/%m/%Y",
25
- errors="coerce"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  )
27
-
28
- datas_invalidas = df['data_encaminhamento'].isna().sum()
29
- if datas_invalidas > 0:
30
- return f"Erro: foram encontradas {datas_invalidas} datas inválidas na coluna 'data_encaminhamento'.", None
31
-
32
- df = df.sort_values(['siat_inscricao', 'data_encaminhamento'])
33
-
34
- chaves = ['siat_inscricao', 'siat_logradouro', 'siat_numero', 'siat_complemento']
35
-
36
- # Agrupamento
37
- metricas = df.groupby(chaves).agg(
38
- primeira_data=('data_encaminhamento', 'min'),
39
- ultima_data=('data_encaminhamento', 'max'),
40
- preco_inicial=('valor_oferta', 'first'),
41
- preco_final=('valor_oferta', 'last'),
42
- preco_minimo=('valor_oferta', 'min'),
43
- contagem_repeticoes=('data_encaminhamento', 'count')
44
- ).reset_index()
45
-
46
- # Cálculos
47
- metricas['variacao_total_rs'] = metricas['preco_final'] - metricas['preco_inicial']
48
- metricas['percentual_reducao'] = np.where(
49
- metricas['preco_inicial'].notna() & (metricas['preco_inicial'] != 0),
50
- (metricas['variacao_total_rs'] / metricas['preco_inicial']) * 100,
51
- np.nan
52
  )
53
- metricas['dias_exposicao'] = (metricas['ultima_data'] - metricas['primeira_data']).dt.days
 
 
 
 
 
 
 
 
 
54
 
55
- # Saneamento de Outliers (IQR)
56
- q1 = metricas['dias_exposicao'].quantile(0.25)
57
- q3 = metricas['dias_exposicao'].quantile(0.75)
 
 
 
 
 
 
58
  iqr = q3 - q1
59
  limite_superior = q3 + 1.5 * iqr
60
- metricas_saneadas = metricas[metricas['dias_exposicao'] <= limite_superior].copy()
61
-
62
- # Diagnóstico para o resumo (markdown)
63
- imoveis_com_desconto = (metricas_saneadas['variacao_total_rs'] < 0).sum()
64
- reducao_media = metricas_saneadas[metricas_saneadas['variacao_total_rs'] < 0]['percentual_reducao'].mean()
65
- tempo_medio = metricas_saneadas['dias_exposicao'].mean()
66
-
67
- resumo = f"""
68
- ### 📊 Diagnóstico de Performance de Preço
69
- - **Imóveis com redução de preço:** {imoveis_com_desconto}
70
- - **Redução média (nos com desconto):** {reducao_media:.2f}%
71
- - **Tempo Médio de Exposição:** {tempo_medio:.1f} dias
72
- """
73
-
74
- return resumo, metricas_saneadas
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
  except Exception as e:
77
- return f"Ocorreu um erro ao processar o arquivo: {str(e)}", None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
- # Interface Gradio
80
- with gr.Blocks(title="Análise de Diagnóstico Imobiliário") as demo:
81
- gr.Markdown("# 🏢 Processador de Diagnóstico de Preços")
82
- gr.Markdown("Faça upload do seu arquivo Excel para calcular variações de preço e tempo de exposição.")
83
-
84
  with gr.Row():
85
- upload_file = gr.File(label="Upload Arquivo Excel (.xlsx)", file_types=[".xlsx"])
86
-
 
 
 
 
87
  with gr.Row():
88
- btn_processar = gr.Button("Processar Diagnóstico", variant="primary")
89
-
90
- with gr.Column():
91
- output_resumo = gr.Markdown()
92
- output_table = gr.DataFrame(label="Métricas Processadas")
93
 
94
  btn_processar.click(
95
  fn=processar_diagnostico_gradio,
96
- inputs=[upload_file],
97
- outputs=[output_resumo, output_table]
98
  )
99
 
100
- # Para rodar no Hugging Face, demo.launch() é suficiente
101
  if __name__ == "__main__":
102
  demo.launch()
 
1
  import pandas as pd
2
  import numpy as np
3
  import gradio as gr
4
+ from pathlib import Path
5
+ import tempfile
6
 
7
+
8
+ def carregar_excel(arquivo):
9
+ """
10
+ Carrega o arquivo Excel enviado pelo Gradio.
11
+ """
12
  if arquivo is None:
13
+ raise ValueError("Por favor, faça o upload de um arquivo Excel.")
14
+
15
+ return pd.read_excel(arquivo.name)
16
+
17
+
18
+ def validar_colunas(df):
19
+ """
20
+ Valida as colunas mínimas necessárias.
21
+ """
22
+ colunas_obrigatorias = [
23
+ "data_encaminhamento",
24
+ "siat_inscricao",
25
+ "siat_logradouro",
26
+ "siat_numero",
27
+ "siat_complemento",
28
+ "valor_oferta",
29
+ ]
30
+
31
+ colunas_faltantes = [col for col in colunas_obrigatorias if col not in df.columns]
32
+
33
+ if colunas_faltantes:
34
+ raise ValueError(
35
+ "As seguintes colunas obrigatórias não foram encontradas no arquivo: "
36
+ + ", ".join(colunas_faltantes)
37
+ )
38
+
39
+
40
+ def preparar_dataframe(df, filtrar_apenas_ofertas=True):
41
+ """
42
+ Faz o pré-processamento da base:
43
+ - filtra ofertas, se possível;
44
+ - converte datas;
45
+ - saneia chaves;
46
+ - saneia valores;
47
+ - ordena os registros.
48
+ """
49
+
50
+ df = df.copy()
51
+
52
+ # Filtrar apenas ofertas, se a coluna existir
53
+ if filtrar_apenas_ofertas and "tipo_informacao" in df.columns:
54
+ df = df[
55
+ df["tipo_informacao"]
56
+ .astype(str)
57
+ .str.lower()
58
+ .str.contains("oferta", na=False)
59
+ ].copy()
60
+
61
+ if df.empty:
62
+ raise ValueError(
63
+ "Após o filtro de ofertas, a base ficou vazia. "
64
+ "Verifique a coluna 'tipo_informacao' ou desative o filtro."
65
+ )
66
+
67
+ # Conversão de datas
68
+ df["data_encaminhamento"] = pd.to_datetime(
69
+ df["data_encaminhamento"],
70
+ dayfirst=True,
71
+ errors="coerce",
72
+ )
73
+
74
+ datas_invalidas = df["data_encaminhamento"].isna().sum()
75
+
76
+ if datas_invalidas > 0:
77
+ raise ValueError(
78
+ f"Foram encontradas {datas_invalidas} datas inválidas "
79
+ "na coluna 'data_encaminhamento'."
80
+ )
81
+
82
+ # Conversão de valores
83
+ df["valor_oferta"] = pd.to_numeric(df["valor_oferta"], errors="coerce")
84
+
85
+ valores_invalidos = df["valor_oferta"].isna().sum()
86
+
87
+ if valores_invalidos > 0:
88
+ raise ValueError(
89
+ f"Foram encontrados {valores_invalidos} valores inválidos "
90
+ "na coluna 'valor_oferta'."
91
+ )
92
+
93
+ # Saneamento das chaves
94
+ chaves = [
95
+ "siat_inscricao",
96
+ "siat_logradouro",
97
+ "siat_numero",
98
+ "siat_complemento",
99
+ ]
100
+
101
+ for col in chaves:
102
+ df[col] = df[col].fillna("").astype(str).str.strip()
103
+
104
+ # Coluna auxiliar para desempate na ordenação
105
+ if "idf_registro" in df.columns:
106
+ coluna_desempate = "idf_registro"
107
+ else:
108
+ df["_ordem_original"] = np.arange(len(df))
109
+ coluna_desempate = "_ordem_original"
110
+
111
+ df = df.sort_values(
112
+ ["siat_inscricao", "data_encaminhamento", coluna_desempate]
113
+ ).copy()
114
+
115
+ return df
116
+
117
+
118
+ def calcular_metricas(df):
119
+ """
120
+ Calcula as métricas por imóvel.
121
+ """
122
+
123
+ chaves = [
124
+ "siat_inscricao",
125
+ "siat_logradouro",
126
+ "siat_numero",
127
+ "siat_complemento",
128
+ ]
129
+
130
+ metricas = df.groupby(chaves, dropna=False).agg(
131
+ primeira_data=("data_encaminhamento", "min"),
132
+ ultima_data=("data_encaminhamento", "max"),
133
+ preco_inicial=("valor_oferta", "first"),
134
+ preco_final=("valor_oferta", "last"),
135
+ preco_minimo=("valor_oferta", "min"),
136
+ preco_maximo=("valor_oferta", "max"),
137
+ contagem_repeticoes=("data_encaminhamento", "count"),
138
+ ).reset_index()
139
+
140
+ metricas["variacao_total_rs"] = (
141
+ metricas["preco_final"] - metricas["preco_inicial"]
142
+ )
143
+
144
+ metricas["variacao_percentual"] = np.where(
145
+ metricas["preco_inicial"].notna() & (metricas["preco_inicial"] != 0),
146
+ (metricas["variacao_total_rs"] / metricas["preco_inicial"]) * 100,
147
+ np.nan,
148
+ )
149
+
150
+ metricas["houve_reducao"] = (
151
+ metricas["preco_final"] < metricas["preco_inicial"]
152
+ )
153
+
154
+ metricas["reducao_percentual"] = np.where(
155
+ metricas["houve_reducao"],
156
+ (
157
+ (metricas["preco_inicial"] - metricas["preco_final"])
158
+ / metricas["preco_inicial"]
159
  )
160
+ * 100,
161
+ 0,
162
+ )
163
+
164
+ metricas["houve_aumento"] = (
165
+ metricas["preco_final"] > metricas["preco_inicial"]
166
+ )
167
+
168
+ metricas["aumento_percentual"] = np.where(
169
+ metricas["houve_aumento"],
170
+ (
171
+ (metricas["preco_final"] - metricas["preco_inicial"])
172
+ / metricas["preco_inicial"]
 
 
 
 
 
 
 
 
 
 
 
 
173
  )
174
+ * 100,
175
+ 0,
176
+ )
177
+
178
+ metricas["dias_exposicao"] = (
179
+ metricas["ultima_data"] - metricas["primeira_data"]
180
+ ).dt.days
181
+
182
+ # Tempo só é observável quando há pelo menos duas aparições
183
+ metricas["tempo_observavel"] = metricas["contagem_repeticoes"] >= 2
184
 
185
+ # Detecção de outlier de tempo somente entre imóveis com tempo observável
186
+ metricas["outlier_dias_exposicao"] = False
187
+ metricas["limite_superior_iqr_dias"] = np.nan
188
+
189
+ metricas_tempo = metricas[metricas["tempo_observavel"]].copy()
190
+
191
+ if len(metricas_tempo) >= 4:
192
+ q1 = metricas_tempo["dias_exposicao"].quantile(0.25)
193
+ q3 = metricas_tempo["dias_exposicao"].quantile(0.75)
194
  iqr = q3 - q1
195
  limite_superior = q3 + 1.5 * iqr
196
+
197
+ metricas["limite_superior_iqr_dias"] = limite_superior
198
+ metricas["outlier_dias_exposicao"] = (
199
+ metricas["tempo_observavel"]
200
+ & (metricas["dias_exposicao"] > limite_superior)
201
+ )
202
+
203
+ # Organização de colunas
204
+ colunas_ordenadas = [
205
+ "siat_inscricao",
206
+ "siat_logradouro",
207
+ "siat_numero",
208
+ "siat_complemento",
209
+ "primeira_data",
210
+ "ultima_data",
211
+ "preco_inicial",
212
+ "preco_final",
213
+ "preco_minimo",
214
+ "preco_maximo",
215
+ "variacao_total_rs",
216
+ "variacao_percentual",
217
+ "houve_reducao",
218
+ "reducao_percentual",
219
+ "houve_aumento",
220
+ "aumento_percentual",
221
+ "contagem_repeticoes",
222
+ "tempo_observavel",
223
+ "dias_exposicao",
224
+ "outlier_dias_exposicao",
225
+ "limite_superior_iqr_dias",
226
+ ]
227
+
228
+ metricas = metricas[colunas_ordenadas]
229
+
230
+ return metricas
231
+
232
+
233
+ def gerar_resumo(metricas, total_linhas_original, total_linhas_filtradas):
234
+ """
235
+ Gera o resumo em Markdown.
236
+ """
237
+
238
+ total_imoveis = len(metricas)
239
+ total_tempo_observavel = int(metricas["tempo_observavel"].sum())
240
+ total_sem_tempo_observavel = int((~metricas["tempo_observavel"]).sum())
241
+
242
+ total_com_reducao = int(metricas["houve_reducao"].sum())
243
+ total_com_aumento = int(metricas["houve_aumento"].sum())
244
+ total_sem_variacao = int(
245
+ (
246
+ metricas["preco_final"].eq(metricas["preco_inicial"])
247
+ ).sum()
248
+ )
249
+
250
+ percentual_com_reducao = (
251
+ total_com_reducao / total_imoveis * 100
252
+ if total_imoveis > 0
253
+ else np.nan
254
+ )
255
+
256
+ reducao_media = metricas.loc[
257
+ metricas["houve_reducao"],
258
+ "reducao_percentual",
259
+ ].mean()
260
+
261
+ reducao_mediana = metricas.loc[
262
+ metricas["houve_reducao"],
263
+ "reducao_percentual",
264
+ ].median()
265
+
266
+ variacao_media_geral = metricas["variacao_percentual"].mean()
267
+ variacao_mediana_geral = metricas["variacao_percentual"].median()
268
+
269
+ metricas_tempo = metricas[metricas["tempo_observavel"]].copy()
270
+ metricas_tempo_sem_outlier = metricas_tempo[
271
+ ~metricas_tempo["outlier_dias_exposicao"]
272
+ ].copy()
273
+
274
+ tempo_medio = metricas_tempo["dias_exposicao"].mean()
275
+ tempo_mediano = metricas_tempo["dias_exposicao"].median()
276
+ tempo_p75 = metricas_tempo["dias_exposicao"].quantile(0.75)
277
+ tempo_p90 = metricas_tempo["dias_exposicao"].quantile(0.90)
278
+
279
+ tempo_medio_sem_outlier = metricas_tempo_sem_outlier["dias_exposicao"].mean()
280
+ tempo_mediano_sem_outlier = metricas_tempo_sem_outlier["dias_exposicao"].median()
281
+
282
+ total_outliers = int(metricas["outlier_dias_exposicao"].sum())
283
+
284
+ limite_iqr = metricas["limite_superior_iqr_dias"].dropna()
285
+ limite_iqr_texto = (
286
+ f"{limite_iqr.iloc[0]:.1f} dias"
287
+ if len(limite_iqr) > 0
288
+ else "não calculado"
289
+ )
290
+
291
+ def fmt(valor, casas=2):
292
+ if pd.isna(valor):
293
+ return "não disponível"
294
+ return f"{valor:.{casas}f}"
295
+
296
+ resumo = f"""
297
+ ## 📊 Diagnóstico de Performance de Preço
298
+
299
+ ### Base processada
300
+
301
+ - **Linhas no arquivo original:** {total_linhas_original}
302
+ - **Linhas utilizadas após filtros:** {total_linhas_filtradas}
303
+ - **Imóveis únicos analisados:** {total_imoveis}
304
+ - **Imóveis com 2 ou mais aparições:** {total_tempo_observavel}
305
+ - **Imóveis com apenas 1 aparição:** {total_sem_tempo_observavel}
306
+
307
+ ### Variação de preço
308
+
309
+ - **Imóveis com redução de preço:** {total_com_reducao} ({fmt(percentual_com_reducao)}%)
310
+ - **Imóveis com aumento de preço:** {total_com_aumento}
311
+ - **Imóveis sem variação de preço:** {total_sem_variacao}
312
+ - **Redução média nos imóveis com desconto:** {fmt(reducao_media)}%
313
+ - **Redução mediana nos imóveis com desconto:** {fmt(reducao_mediana)}%
314
+ - **Variação percentual média geral:** {fmt(variacao_media_geral)}%
315
+ - **Variação percentual mediana geral:** {fmt(variacao_mediana_geral)}%
316
+
317
+ ### Tempo observado de exposição
318
+
319
+ > O tempo de exposição é calculado apenas para imóveis com pelo menos duas aparições.
320
+ > Imóveis com uma única aparição não são tratados como “0 dia de exposição”, mas como tempo não observável.
321
+
322
+ - **Tempo médio observado:** {fmt(tempo_medio, 1)} dias
323
+ - **Tempo mediano observado:** {fmt(tempo_mediano, 1)} dias
324
+ - **Percentil 75:** {fmt(tempo_p75, 1)} dias
325
+ - **Percentil 90:** {fmt(tempo_p90, 1)} dias
326
+
327
+ ### Outliers de tempo
328
+
329
+ > Os outliers são apenas marcados, não removidos automaticamente.
330
+
331
+ - **Limite superior pelo critério IQR:** {limite_iqr_texto}
332
+ - **Quantidade de outliers de tempo:** {total_outliers}
333
+ - **Tempo médio sem outliers:** {fmt(tempo_medio_sem_outlier, 1)} dias
334
+ - **Tempo mediano sem outliers:** {fmt(tempo_mediano_sem_outlier, 1)} dias
335
+
336
+ ### Leitura técnica
337
+
338
+ Este resultado mede a **permanência observada das ofertas na base** e a **variação entre a primeira e a última oferta observada**.
339
+ Ele não deve ser interpretado, sozinho, como taxa de absorção estrita, pois taxa de absorção exigiria relação entre estoque ofertado e imóveis efetivamente transacionados/vendidos.
340
+ """
341
+
342
+ return resumo
343
+
344
+
345
+ def exportar_excel(metricas):
346
+ """
347
+ Exporta as métricas para um arquivo Excel temporário.
348
+ """
349
+
350
+ caminho_saida = Path(tempfile.gettempdir()) / "diagnostico_imobiliario_metricas.xlsx"
351
+
352
+ with pd.ExcelWriter(caminho_saida, engine="openpyxl") as writer:
353
+ metricas.to_excel(writer, index=False, sheet_name="metricas")
354
+
355
+ ws = writer.book["metricas"]
356
+
357
+ # Ajuste simples de largura das colunas
358
+ for coluna in ws.columns:
359
+ tamanho_maximo = 0
360
+ letra_coluna = coluna[0].column_letter
361
+
362
+ for celula in coluna:
363
+ try:
364
+ tamanho_maximo = max(tamanho_maximo, len(str(celula.value)))
365
+ except Exception:
366
+ pass
367
+
368
+ ws.column_dimensions[letra_coluna].width = min(tamanho_maximo + 2, 40)
369
+
370
+ return str(caminho_saida)
371
+
372
+
373
+ def processar_diagnostico_gradio(arquivo, filtrar_apenas_ofertas):
374
+ """
375
+ Função principal chamada pelo Gradio.
376
+ """
377
+
378
+ try:
379
+ df_original = carregar_excel(arquivo)
380
+ total_linhas_original = len(df_original)
381
+
382
+ validar_colunas(df_original)
383
+
384
+ df_preparado = preparar_dataframe(
385
+ df_original,
386
+ filtrar_apenas_ofertas=filtrar_apenas_ofertas,
387
+ )
388
+
389
+ total_linhas_filtradas = len(df_preparado)
390
+
391
+ metricas = calcular_metricas(df_preparado)
392
+
393
+ resumo = gerar_resumo(
394
+ metricas=metricas,
395
+ total_linhas_original=total_linhas_original,
396
+ total_linhas_filtradas=total_linhas_filtradas,
397
+ )
398
+
399
+ arquivo_excel = exportar_excel(metricas)
400
+
401
+ return resumo, metricas, arquivo_excel
402
 
403
  except Exception as e:
404
+ mensagem_erro = f"""
405
+ ## Erro ao processar o arquivo
406
+
407
+ {str(e)}
408
+ """
409
+ return mensagem_erro, None, None
410
+
411
+
412
+ css = """
413
+ .gradio-container {
414
+ max-width: 1400px !important;
415
+ margin: auto;
416
+ }
417
+
418
+ #titulo-app {
419
+ text-align: center;
420
+ margin-bottom: 8px;
421
+ }
422
+
423
+ #subtitulo-app {
424
+ text-align: center;
425
+ color: #555;
426
+ margin-bottom: 24px;
427
+ }
428
+
429
+ .resumo-box {
430
+ border-radius: 12px;
431
+ padding: 16px;
432
+ }
433
+ """
434
+
435
+
436
+ with gr.Blocks(
437
+ title="Diagnóstico Imobiliário",
438
+ css=css,
439
+ theme=gr.themes.Soft(),
440
+ ) as demo:
441
+
442
+ gr.Markdown(
443
+ """
444
+ # 🏢 Diagnóstico de Performance de Preços
445
+ """,
446
+ elem_id="titulo-app",
447
+ )
448
+
449
+ gr.Markdown(
450
+ """
451
+ Calcule a variação entre a primeira e a última oferta observada e estime o tempo observado de exposição dos imóveis no mercado.
452
+ """,
453
+ elem_id="subtitulo-app",
454
+ )
455
+
456
+ with gr.Row():
457
+ with gr.Column(scale=2):
458
+ upload_file = gr.File(
459
+ label="Arquivo Excel",
460
+ file_types=[".xlsx"],
461
+ )
462
+
463
+ with gr.Column(scale=1):
464
+ filtrar_ofertas = gr.Checkbox(
465
+ label="Filtrar apenas registros de oferta",
466
+ value=True,
467
+ info="Usa a coluna 'tipo_informacao', se ela existir.",
468
+ )
469
+
470
+ btn_processar = gr.Button(
471
+ "Processar Diagnóstico",
472
+ variant="primary",
473
+ )
474
+
475
+ with gr.Row():
476
+ output_resumo = gr.Markdown(
477
+ label="Resumo do Diagnóstico",
478
+ elem_classes=["resumo-box"],
479
+ )
480
 
 
 
 
 
 
481
  with gr.Row():
482
+ output_table = gr.DataFrame(
483
+ label="Métricas Processadas",
484
+ interactive=False,
485
+ wrap=True,
486
+ )
487
+
488
  with gr.Row():
489
+ output_excel = gr.File(
490
+ label="Baixar resultado em Excel",
491
+ )
 
 
492
 
493
  btn_processar.click(
494
  fn=processar_diagnostico_gradio,
495
+ inputs=[upload_file, filtrar_ofertas],
496
+ outputs=[output_resumo, output_table, output_excel],
497
  )
498
 
499
+
500
  if __name__ == "__main__":
501
  demo.launch()