Spaces:
Sleeping
Sleeping
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 |
}
|