Guilherme Silberfarb Costa commited on
Commit
ca6595d
·
1 Parent(s): d0f2c73

alteracoes nos outliers

Browse files
backend/app/services/elaboracao_service.py CHANGED
@@ -945,6 +945,12 @@ def fit_model(
945
  for col in session.colunas_x:
946
  if col in df_base.columns and col not in tabela_metricas.columns:
947
  tabela_metricas[col] = df_base[col].values
 
 
 
 
 
 
948
 
949
  # Estado completo para filtros: mantém todas as colunas originais disponíveis.
950
  colunas_novas = {
@@ -968,6 +974,7 @@ def fit_model(
968
 
969
  n_out = len(session.outliers_anteriores)
970
  resumo = f"Excluidos: {n_out} | A excluir: 0 | A reincluir: 0 | Total: {n_out}"
 
971
 
972
  return {
973
  "diagnosticos_html": diagnosticos_html,
@@ -988,6 +995,7 @@ def fit_model(
988
  "grafico_cook": figure_to_payload(graficos.get("cook")),
989
  "grafico_correlacao": figure_to_payload(fig_corr),
990
  "tabela_metricas": dataframe_to_payload(tabela_metricas, decimals=4),
 
991
  "variaveis_filtro": variaveis_filtro,
992
  "resumo_outliers": resumo,
993
  "avaliacao_campos": build_campos_avaliacao(session),
@@ -995,6 +1003,42 @@ def fit_model(
995
  }
996
 
997
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
998
  def gerar_grafico_dispersao_modelo(session: SessionState, tipo: str) -> dict[str, Any]:
999
  if not session.resultado_modelo:
1000
  raise HTTPException(status_code=400, detail="Ajuste um modelo primeiro")
 
945
  for col in session.colunas_x:
946
  if col in df_base.columns and col not in tabela_metricas.columns:
947
  tabela_metricas[col] = df_base[col].values
948
+ if (
949
+ session.coluna_data_mercado
950
+ and session.coluna_data_mercado in df_base.columns
951
+ and session.coluna_data_mercado not in tabela_metricas.columns
952
+ ):
953
+ tabela_metricas[session.coluna_data_mercado] = df_base[session.coluna_data_mercado].values
954
 
955
  # Estado completo para filtros: mantém todas as colunas originais disponíveis.
956
  colunas_novas = {
 
974
 
975
  n_out = len(session.outliers_anteriores)
976
  resumo = f"Excluidos: {n_out} | A excluir: 0 | A reincluir: 0 | Total: {n_out}"
977
+ tabela_outliers_excluidos = _montar_tabela_outliers_excluidos(session)
978
 
979
  return {
980
  "diagnosticos_html": diagnosticos_html,
 
995
  "grafico_cook": figure_to_payload(graficos.get("cook")),
996
  "grafico_correlacao": figure_to_payload(fig_corr),
997
  "tabela_metricas": dataframe_to_payload(tabela_metricas, decimals=4),
998
+ "tabela_outliers_excluidos": tabela_outliers_excluidos,
999
  "variaveis_filtro": variaveis_filtro,
1000
  "resumo_outliers": resumo,
1001
  "avaliacao_campos": build_campos_avaliacao(session),
 
1003
  }
1004
 
1005
 
1006
+ def _montar_tabela_outliers_excluidos(session: SessionState) -> dict[str, Any] | None:
1007
+ df_base = session.df_original
1008
+ if df_base is None:
1009
+ return None
1010
+
1011
+ indices_excluidos = _clean_int_list(session.outliers_anteriores)
1012
+ if not indices_excluidos:
1013
+ return None
1014
+
1015
+ indices_presentes = [idx for idx in indices_excluidos if idx in df_base.index]
1016
+ if not indices_presentes:
1017
+ return None
1018
+
1019
+ colunas_tabela: list[str] = []
1020
+ if session.coluna_y and session.coluna_y in df_base.columns:
1021
+ colunas_tabela.append(session.coluna_y)
1022
+ for col in session.colunas_x or []:
1023
+ if col in df_base.columns and col not in colunas_tabela:
1024
+ colunas_tabela.append(col)
1025
+ if (
1026
+ session.coluna_data_mercado
1027
+ and session.coluna_data_mercado in df_base.columns
1028
+ and session.coluna_data_mercado not in colunas_tabela
1029
+ ):
1030
+ colunas_tabela.append(session.coluna_data_mercado)
1031
+
1032
+ if colunas_tabela:
1033
+ tabela_df = df_base.loc[indices_presentes, colunas_tabela].copy()
1034
+ else:
1035
+ tabela_df = pd.DataFrame(index=indices_presentes)
1036
+
1037
+ tabela_df.insert(0, "Índice", tabela_df.index)
1038
+ tabela_df = tabela_df.reset_index(drop=True)
1039
+ return dataframe_to_payload(tabela_df, decimals=4)
1040
+
1041
+
1042
  def gerar_grafico_dispersao_modelo(session: SessionState, tipo: str) -> dict[str, Any]:
1043
  if not session.resultado_modelo:
1044
  raise HTTPException(status_code=400, detail="Ajuste um modelo primeiro")
frontend/src/components/ElaboracaoTab.jsx CHANGED
@@ -94,6 +94,22 @@ function buildOutlierTextSnapshot(outliersTexto, reincluirTexto) {
94
  })
95
  }
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  function toFiniteNumber(value) {
98
  if (typeof value === 'number') {
99
  return Number.isFinite(value) ? value : null
@@ -920,6 +936,14 @@ export default function ElaboracaoTab({ sessionId }) {
920
  () => Array.isArray(baseChoices) && baseChoices.length > 0,
921
  [baseChoices],
922
  )
 
 
 
 
 
 
 
 
923
  const showCoordsPanel = Boolean(
924
  coordsInfo && (
925
  !coordsInfo.tem_coords ||
@@ -3605,6 +3629,18 @@ export default function ElaboracaoTab({ sessionId }) {
3605
  {outliersAnteriores.length > 0 && outliersHtml ? (
3606
  <div className="outliers-html-box" dangerouslySetInnerHTML={{ __html: outliersHtml }} />
3607
  ) : null}
 
 
 
 
 
 
 
 
 
 
 
 
3608
 
3609
  <div className="outlier-group-card">
3610
  <div className="outlier-subheader">Filtrar outliers</div>
@@ -3678,14 +3714,15 @@ export default function ElaboracaoTab({ sessionId }) {
3678
  <input type="text" value={reincluirTexto} onChange={(e) => setReincluirTexto(e.target.value)} placeholder="ex: 5" />
3679
  </div>
3680
  </div>
 
 
 
3681
  <div className="outlier-actions-row">
3682
  <button onClick={onRestartIteration} disabled={loading} className="btn-reiniciar-iteracao">
3683
  Atualizar Modelo (Excluir/Reincluir Outliers)
3684
  </button>
3685
  </div>
3686
  </div>
3687
- <div className="resumo-outliers-box">Iteração: {iteracao} | {resumoOutliers}</div>
3688
- <div className="resumo-outliers-box">Outliers anteriores: {joinSelection(outliersAnteriores) || '-'}</div>
3689
  </SectionBlock>
3690
 
3691
  <SectionBlock step="18" title="Avaliação de Imóvel" subtitle="Cálculo individual e comparação entre avaliações.">
 
94
  })
95
  }
96
 
97
+ function parseIndicesFromText(value) {
98
+ const tokens = String(value || '')
99
+ .split(/[\s,;]+/)
100
+ .map((item) => item.trim())
101
+ .filter(Boolean)
102
+
103
+ const indices = new Set()
104
+ tokens.forEach((token) => {
105
+ if (!/^-?\d+$/.test(token)) return
106
+ const parsed = Number(token)
107
+ if (!Number.isInteger(parsed)) return
108
+ indices.add(parsed)
109
+ })
110
+ return Array.from(indices)
111
+ }
112
+
113
  function toFiniteNumber(value) {
114
  if (typeof value === 'number') {
115
  return Number.isFinite(value) ? value : null
 
936
  () => Array.isArray(baseChoices) && baseChoices.length > 0,
937
  [baseChoices],
938
  )
939
+ const qtdListaExcluir = useMemo(
940
+ () => parseIndicesFromText(outliersTexto).length,
941
+ [outliersTexto],
942
+ )
943
+ const qtdListaReincluir = useMemo(
944
+ () => parseIndicesFromText(reincluirTexto).length,
945
+ [reincluirTexto],
946
+ )
947
  const showCoordsPanel = Boolean(
948
  coordsInfo && (
949
  !coordsInfo.tem_coords ||
 
3629
  {outliersAnteriores.length > 0 && outliersHtml ? (
3630
  <div className="outliers-html-box" dangerouslySetInnerHTML={{ __html: outliersHtml }} />
3631
  ) : null}
3632
+ {outliersAnteriores.length > 0 ? (
3633
+ <details className="outliers-excluidos-details">
3634
+ <summary>Mostrar tabela dos outliers excluídos</summary>
3635
+ <div className="outliers-excluidos-table-wrap">
3636
+ {fit.tabela_outliers_excluidos ? (
3637
+ <DataTable table={fit.tabela_outliers_excluidos} maxHeight={280} />
3638
+ ) : (
3639
+ <div className="section1-empty-hint">Tabela indisponível para os outliers excluídos.</div>
3640
+ )}
3641
+ </div>
3642
+ </details>
3643
+ ) : null}
3644
 
3645
  <div className="outlier-group-card">
3646
  <div className="outlier-subheader">Filtrar outliers</div>
 
3714
  <input type="text" value={reincluirTexto} onChange={(e) => setReincluirTexto(e.target.value)} placeholder="ex: 5" />
3715
  </div>
3716
  </div>
3717
+ <div className="resumo-outliers-box">
3718
+ Já excluídos: {outliersAnteriores.length} | Na lista para reinclusão: {qtdListaReincluir} | Na lista para exclusão: {qtdListaExcluir}
3719
+ </div>
3720
  <div className="outlier-actions-row">
3721
  <button onClick={onRestartIteration} disabled={loading} className="btn-reiniciar-iteracao">
3722
  Atualizar Modelo (Excluir/Reincluir Outliers)
3723
  </button>
3724
  </div>
3725
  </div>
 
 
3726
  </SectionBlock>
3727
 
3728
  <SectionBlock step="18" title="Avaliação de Imóvel" subtitle="Cálculo individual e comparação entre avaliações.">
frontend/src/styles.css CHANGED
@@ -2992,6 +2992,46 @@ button.btn-upload-select {
2992
  margin-top: 8px;
2993
  }
2994
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2995
  .transformacoes-aplicadas-wrap {
2996
  margin-top: 12px;
2997
  }
 
2992
  margin-top: 8px;
2993
  }
2994
 
2995
+ .outliers-excluidos-details {
2996
+ margin-top: 10px;
2997
+ border: 1px solid #dbe6f1;
2998
+ border-radius: 11px;
2999
+ background: #fbfdff;
3000
+ padding: 8px 10px;
3001
+ }
3002
+
3003
+ .outliers-excluidos-details > summary {
3004
+ list-style: none;
3005
+ cursor: pointer;
3006
+ user-select: none;
3007
+ color: #3f5973;
3008
+ font-weight: 800;
3009
+ font-size: 0.84rem;
3010
+ display: inline-flex;
3011
+ align-items: center;
3012
+ gap: 6px;
3013
+ }
3014
+
3015
+ .outliers-excluidos-details > summary::-webkit-details-marker {
3016
+ display: none;
3017
+ }
3018
+
3019
+ .outliers-excluidos-details > summary::before {
3020
+ content: '▸';
3021
+ color: #68829c;
3022
+ font-size: 0.8rem;
3023
+ line-height: 1;
3024
+ transition: transform 0.15s ease;
3025
+ }
3026
+
3027
+ .outliers-excluidos-details[open] > summary::before {
3028
+ transform: rotate(90deg);
3029
+ }
3030
+
3031
+ .outliers-excluidos-table-wrap {
3032
+ margin-top: 10px;
3033
+ }
3034
+
3035
  .transformacoes-aplicadas-wrap {
3036
  margin-top: 12px;
3037
  }