ui: focus mechanic workflow and clarify health map
All checks were successful
ENGIN CI / Build, test and smoke (push) Successful in 36s
All checks were successful
ENGIN CI / Build, test and smoke (push) Successful in 36s
This commit is contained in:
parent
7159c01123
commit
dab5264ed7
68
app.py
68
app.py
@ -15,8 +15,6 @@ from engin.charts import cylinder_spectrum, deviation_chart, engine_heatmap
|
||||
from engin.config import (
|
||||
LABEL_COLORS,
|
||||
LABEL_DISPLAY,
|
||||
LABEL_ICONS,
|
||||
NOT_APPLICABLE,
|
||||
SEVERITY_DISPLAY,
|
||||
AppConfig,
|
||||
)
|
||||
@ -38,7 +36,6 @@ MODEL_VERSION = "engin-2026.08.25-1"
|
||||
MODEL_ARTIFACT_DIR = BASE_DIR / "artifacts" / MODEL_VERSION
|
||||
VIEW_OVERVIEW = "Przegląd"
|
||||
VIEW_DETAIL = "Szczegóły cylindra"
|
||||
VIEW_MODEL = "Model"
|
||||
PLOTLY_CONFIG = {"displayModeBar": False, "displaylogo": False}
|
||||
DIAGNOSTIC_COLUMN_DISPLAY = {
|
||||
"engine_id": "Silnik",
|
||||
@ -155,7 +152,14 @@ def _diagnostics_for_display(frame: pd.DataFrame) -> pd.DataFrame:
|
||||
|
||||
|
||||
def _render_header(live_inference: bool) -> None:
|
||||
mode = "MODEL AKTYWNY" if live_inference else "TRYB DEMONSTRACYJNY"
|
||||
if live_inference:
|
||||
mode = "MODEL GOTOWY"
|
||||
verification = "ARTEFAKT ZWERYFIKOWANY"
|
||||
tone = "verified"
|
||||
else:
|
||||
mode = "TRYB DEMONSTRACYJNY"
|
||||
verification = "ZAPISANE WYNIKI"
|
||||
tone = "fallback"
|
||||
st.markdown(
|
||||
f"""
|
||||
<div class="product-header">
|
||||
@ -164,7 +168,7 @@ def _render_header(live_inference: bool) -> None:
|
||||
<h1>Konsola diagnostyczna ENGIN</h1>
|
||||
<p>Diagnoza cylindra, nasilenie i następny krok.</p>
|
||||
</div>
|
||||
<div class="runtime-badge">● {mode}<br><span>CPU · DANE LOKALNE</span></div>
|
||||
<div class="runtime-badge runtime-{tone}">● {mode}<br><span>{verification}</span></div>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True,
|
||||
@ -185,35 +189,6 @@ def _render_summary(summary) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _select_cylinder(session_key: str, view_key: str, cylinder: int) -> None:
|
||||
st.session_state[session_key] = cylinder
|
||||
st.session_state[view_key] = VIEW_DETAIL
|
||||
|
||||
|
||||
def _render_cylinder_grid(analysis, session_key: str, view_key: str) -> None:
|
||||
st.markdown("### Mapa cylindrów")
|
||||
columns = st.columns(4)
|
||||
selected_cylinder = int(st.session_state[session_key])
|
||||
for index, row in analysis.diagnostics.iterrows():
|
||||
cylinder = int(row["cylinder"])
|
||||
label = str(row["label"])
|
||||
severity = str(row["severity"])
|
||||
icon = LABEL_ICONS[label]
|
||||
short_label = LABEL_DISPLAY[label]
|
||||
if severity != NOT_APPLICABLE:
|
||||
short_label += f" · {SEVERITY_DISPLAY[severity]}"
|
||||
button_label = f"{icon} C{cylinder:02d}\n{short_label}"
|
||||
with columns[index % 4]:
|
||||
st.button(
|
||||
button_label,
|
||||
key=f"cylinder_{analysis.engine_id}_{cylinder}",
|
||||
width="stretch",
|
||||
type="primary" if cylinder == selected_cylinder else "secondary",
|
||||
on_click=_select_cylinder,
|
||||
args=(session_key, view_key, cylinder),
|
||||
)
|
||||
|
||||
|
||||
def _render_engine_overview(analysis) -> None:
|
||||
left, right = st.columns(2, gap="large")
|
||||
with left:
|
||||
@ -311,14 +286,14 @@ def _render_technical(result: DiagnosisResult) -> None:
|
||||
c4.metric("Testy", "38 / 38")
|
||||
st.caption(
|
||||
"Walidacja grupowa według silników · średnia z 5 uruchomień · "
|
||||
"makro F1 przy 5% braków: 0.983 · CPU."
|
||||
"makro F1 przy 5% braków: 0.983 · wnioskowanie lokalne na CPU."
|
||||
)
|
||||
st.markdown("#### Dane diagnostyczne")
|
||||
st.dataframe(
|
||||
_diagnostics_for_display(result.diagnostics),
|
||||
width="stretch",
|
||||
hide_index=True,
|
||||
)
|
||||
with st.expander("Pokaż dane diagnostyczne"):
|
||||
st.dataframe(
|
||||
_diagnostics_for_display(result.diagnostics),
|
||||
width="stretch",
|
||||
hide_index=True,
|
||||
)
|
||||
|
||||
|
||||
def render_app(dependencies: AppDependencies | None = None) -> None:
|
||||
@ -388,10 +363,7 @@ def render_app(dependencies: AppDependencies | None = None) -> None:
|
||||
width="stretch",
|
||||
)
|
||||
st.divider()
|
||||
st.caption(
|
||||
f"Wersja modelu: {deps.model_version} · wnioskowanie na CPU · "
|
||||
"bez połączeń zewnętrznych"
|
||||
)
|
||||
st.caption(f"Wersja modelu: {deps.model_version}")
|
||||
|
||||
analysis = analyze_engine(result, str(engine_id))
|
||||
summary = summarize_engine(analysis)
|
||||
@ -407,7 +379,7 @@ def render_app(dependencies: AppDependencies | None = None) -> None:
|
||||
st.session_state[view_key] = VIEW_OVERVIEW
|
||||
view = st.segmented_control(
|
||||
"Widok",
|
||||
[VIEW_OVERVIEW, VIEW_DETAIL, VIEW_MODEL],
|
||||
[VIEW_OVERVIEW, VIEW_DETAIL],
|
||||
required=True,
|
||||
key=view_key,
|
||||
label_visibility="collapsed",
|
||||
@ -415,7 +387,6 @@ def render_app(dependencies: AppDependencies | None = None) -> None:
|
||||
)
|
||||
|
||||
if view == VIEW_OVERVIEW:
|
||||
_render_cylinder_grid(analysis, session_key, view_key)
|
||||
_render_engine_overview(analysis)
|
||||
elif view == VIEW_DETAIL:
|
||||
selector_col, comparison_col = st.columns([1.0, 2.0], gap="large")
|
||||
@ -449,7 +420,8 @@ def render_app(dependencies: AppDependencies | None = None) -> None:
|
||||
int(selected_from_box),
|
||||
comparison_cylinders,
|
||||
)
|
||||
elif view == VIEW_MODEL:
|
||||
|
||||
with st.expander("Informacje techniczne", expanded=False):
|
||||
_render_technical(result)
|
||||
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
--green: #38d996;
|
||||
--orange: #f5a524;
|
||||
--red: #ff6b57;
|
||||
--radius: 6px;
|
||||
}
|
||||
|
||||
[data-testid="stAppViewContainer"] {
|
||||
@ -32,27 +33,35 @@
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.product-header h1 { margin: .2rem 0 .25rem; color: #f3f8fa; font-size: 2rem; letter-spacing: -.035em; }
|
||||
.product-header h1 { margin: .2rem 0 .25rem; color: #f3f8fa; font-size: 2rem; line-height: 1.12; letter-spacing: -.035em; }
|
||||
.product-header p { margin: 0; color: var(--muted); }
|
||||
.eyebrow { color: var(--cyan); font-size: .7rem; font-weight: 800; letter-spacing: .17em; }
|
||||
.runtime-badge {
|
||||
min-width: 170px;
|
||||
min-width: 218px;
|
||||
padding: .7rem .9rem;
|
||||
border: 1px solid rgba(56, 217, 150, .32);
|
||||
border-radius: var(--radius);
|
||||
background: rgba(56, 217, 150, .07);
|
||||
color: var(--green);
|
||||
font: 700 .72rem/1.35 ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
letter-spacing: .06em;
|
||||
}
|
||||
.runtime-badge span { color: var(--muted); font-size: .64rem; }
|
||||
.runtime-fallback {
|
||||
border-color: rgba(245, 165, 36, .38);
|
||||
background: rgba(245, 165, 36, .08);
|
||||
color: var(--orange);
|
||||
}
|
||||
|
||||
.status-strip {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
border: 1px solid var(--line);
|
||||
border-left: 4px solid var(--green);
|
||||
border-radius: var(--radius);
|
||||
background: linear-gradient(90deg, rgba(56, 217, 150, .07), rgba(16, 27, 34, .92));
|
||||
margin-bottom: 1.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
.status-strip > div { padding: 1rem 1.2rem; border-right: 1px solid var(--line); }
|
||||
.status-strip > div:last-child { border-right: 0; }
|
||||
@ -60,7 +69,10 @@
|
||||
.status-strip strong { display: block; margin-top: .2rem; font-size: 1.28rem; color: var(--text); }
|
||||
.status-warning { border-left-color: var(--orange); background: linear-gradient(90deg, rgba(245, 165, 36, .08), rgba(16, 27, 34, .92)); }
|
||||
.status-critical { border-left-color: var(--red); background: linear-gradient(90deg, rgba(255, 107, 87, .09), rgba(16, 27, 34, .92)); }
|
||||
.status-unknown { border-left-color: #d56cff; }
|
||||
.status-unknown {
|
||||
border-left-color: #d56cff;
|
||||
background: linear-gradient(90deg, rgba(213, 108, 255, .08), rgba(16, 27, 34, .92));
|
||||
}
|
||||
|
||||
.diagnosis-card {
|
||||
--diagnosis-color: var(--cyan);
|
||||
@ -73,6 +85,7 @@
|
||||
background: linear-gradient(90deg, color-mix(in srgb, var(--diagnosis-color) 12%, transparent), var(--panel));
|
||||
border: 1px solid var(--line);
|
||||
border-left: 4px solid var(--diagnosis-color);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
.diagnosis-card h2 { margin: .08rem 0; color: var(--text); }
|
||||
.diagnosis-card p { margin: 0; color: var(--muted); }
|
||||
@ -84,19 +97,31 @@
|
||||
}
|
||||
.diagnosis-metrics strong { display: block; margin-top: .2rem; color: var(--text); font-size: 1.05rem; }
|
||||
|
||||
[data-testid="stButton"] button {
|
||||
min-height: 4.2rem;
|
||||
white-space: pre-line;
|
||||
border-radius: 3px;
|
||||
border-color: var(--line);
|
||||
[data-testid="stMetric"] {
|
||||
min-height: 5.2rem;
|
||||
padding: .75rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
background: var(--panel);
|
||||
font-weight: 650;
|
||||
}
|
||||
[data-testid="stButton"] button:hover { border-color: var(--cyan); color: #fff; }
|
||||
[data-testid="stButton"] button[kind="primary"] { border-color: var(--cyan); background: rgba(40, 183, 217, .13); }
|
||||
|
||||
[data-testid="stMetric"] { padding: .7rem; border: 1px solid var(--line); background: var(--panel); }
|
||||
[data-testid="stDataFrame"], [data-testid="stPlotlyChart"] { border: 1px solid var(--line); overflow: hidden; }
|
||||
[data-testid="stDataFrame"], [data-testid="stPlotlyChart"] {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
background: var(--panel);
|
||||
overflow: hidden;
|
||||
}
|
||||
[data-testid="stDownloadButton"] button,
|
||||
[data-testid="stFileUploaderDropzone"] {
|
||||
border-color: var(--line);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
[data-testid="stDownloadButton"] button:hover { border-color: var(--cyan); }
|
||||
[data-testid="stExpander"] details {
|
||||
border-color: var(--line);
|
||||
border-radius: var(--radius);
|
||||
background: rgba(16, 27, 34, .58);
|
||||
}
|
||||
[data-baseweb="select"] > div { border-radius: var(--radius); }
|
||||
|
||||
/* Streamlit 1.62 has no interface locale setting, so localize its uploader chrome. */
|
||||
[data-testid="stFileUploaderDropzone"] button [data-testid="stMarkdownContainer"] { display: none; }
|
||||
@ -123,7 +148,9 @@
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.product-header h1 { font-size: 1.65rem; }
|
||||
.status-strip { grid-template-columns: 1fr; }
|
||||
.status-strip > div { border-right: 0; border-bottom: 1px solid var(--line); }
|
||||
.status-strip > div:last-child { border-bottom: 0; }
|
||||
.diagnosis-metrics { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
## 0:35–1:15 — ekran silnika
|
||||
|
||||
1. Uruchom `streamlit run app.py` i pozostaw „Dane demonstracyjne”.
|
||||
2. Wskaż status silnika, najwyższe severity i liczbę cylindrów wymagających uwagi.
|
||||
3. Pokaż mapę cylindrów oraz ranking kontroli.
|
||||
2. Wskaż status silnika, najwyższe nasilenie i liczbę cylindrów wymagających uwagi.
|
||||
3. Pokaż mapę odchyleń: diagnoza jest zapisana przy każdym cylindrze, a stała skala kolorów pozwala porównywać silniki.
|
||||
|
||||
Komunikat: „Nie zmuszamy mechanika do czytania 21 wykresów. Najpierw widzi najważniejszy cylinder i pilność.”
|
||||
|
||||
@ -16,29 +16,30 @@ Komunikat: „Nie zmuszamy mechanika do czytania 21 wykresów. Najpierw widzi na
|
||||
|
||||
1. Otwórz „Szczegóły cylindra”.
|
||||
2. Wybierz najwyżej sklasyfikowany cylinder.
|
||||
3. Porównaj jego widmo z medianą pozostałych cylindrów tego samego silnika.
|
||||
4. Wskaż zaznaczone pasma, score modelu, severity i rekomendowany następny krok.
|
||||
3. Opcjonalnie dodaj drugi cylinder do porównania.
|
||||
4. Porównaj widmo z medianą pozostałych cylindrów tego samego silnika.
|
||||
5. Wskaż zaznaczone pasma, wynik modelu, nasilenie i rekomendowany następny krok.
|
||||
|
||||
Komunikat: „Referencją jest konkretny silnik, nie abstrakcyjna średnia całej floty. Dzięki temu wynik jest łatwiejszy do zweryfikowania przez człowieka.”
|
||||
|
||||
## 2:20–3:05 — odporność
|
||||
|
||||
1. Przejdź do „Walidacja i model”.
|
||||
2. Pokaż grouped Macro F1 0.981, severity 0.930 i wynik przy 5% braków.
|
||||
3. Powiedz, że cały silnik jest zawsze w jednym foldzie i że inference działa na CPU.
|
||||
1. Rozwiń „Informacje techniczne”.
|
||||
2. Pokaż grupowe makro F1 0.981, trafność nasilenia 0.930 i wynik przy 5% braków.
|
||||
3. Powiedz, że cały silnik jest zawsze w jednym zbiorze walidacyjnym, a model działa na CPU.
|
||||
|
||||
Komunikat: „Wynik nie powstał przez przeciek między cylindrami tego samego silnika. Pipeline jest też testowany na brakach odpowiadających realnym danym warsztatowym.”
|
||||
|
||||
## 3:05–3:40 — własny plik i wartość
|
||||
|
||||
1. Pokaż opcję „Wgraj plik CSV”, ale nie przełączaj jej, jeśli czas jest napięty.
|
||||
2. Pokaż przyciski pobrania submission oraz pełnej diagnostyki.
|
||||
2. Pokaż przyciski pobrania wyników oraz pełnej diagnostyki.
|
||||
|
||||
Zamknięcie: „ENGIN daje trzy rzeczy naraz: konkursowo skuteczny model, weryfikowalne uzasadnienie i prostą decyzję serwisową. Działa lokalnie na CPU i jest gotowy na podłączenie do warsztatowego procesu.”
|
||||
|
||||
## Plan awaryjny
|
||||
|
||||
- Jeśli nie ma internetu: aplikacja nie potrzebuje internetu.
|
||||
- Jeśli model nie wystartuje: automatycznie działa fallback na zapisanych predykcjach demo.
|
||||
- Jeśli model nie wystartuje: automatycznie działa tryb awaryjny na zapisanych predykcjach demonstracyjnych.
|
||||
- Jeśli brakuje czasu: pokaż ekran główny, jeden cylinder i metryki — około 90 sekund.
|
||||
- Przed wystąpieniem uruchom `python -m unittest discover -v` i zachowaj terminal z wynikiem 38/38.
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
|
||||
| Uwaga z przeglądu | Zmiana | Dowód regresji |
|
||||
| --- | --- | --- |
|
||||
| Mapa cylindrów i dropdown nadpisywały sobie stan | oba widgety korzystają z jednego klucza session state; kafelki ustawiają go callbackiem | `test_cylinder_grid_and_detail_selector_share_state` |
|
||||
| Wybór cylindra niepotrzebnie obciążał przegląd silnika | wybór jest dostępny wyłącznie w widoku szczegółów; przegląd pokazuje tylko stan i priorytety | `test_cylinder_selector_exists_only_in_detail_view` |
|
||||
| Automatyczna skala mapy wyolbrzymiała różnice w zdrowych silnikach | mapa ma stałą skalę ±20 mV i opis diagnozy przy każdym cylindrze | `test_all_chart_factories_return_populated_figures` |
|
||||
| Status `WYMAGA WERYFIKACJI` był nieosiągalny | status zależy bezpośrednio od label/severity; `unknown` bez nazwanej usterki ma dedykowany status | `test_unknown_only_engine_requires_verification` |
|
||||
| Engine Health 0–100 był arbitralny | liczba została usunięta; UI pokazuje najwyższe rzeczywiste severity i porządkowy triage | testy explainability + smoke UI |
|
||||
| OOD otrzymywał sztuczny confidence 50–99% | score OOD jest `NaN`; UI nazywa pozostałe wartości score modelu i wyjaśnia brak kalibracji | `test_diagnostics_are_complete_for_the_application` |
|
||||
@ -15,4 +16,4 @@ Po zmianie OOD ponownie wykonano pięć seedów grouped CV dla clean i masked 5%
|
||||
- clean: Macro F1 `0.9811`, severity `0.9298`, Raw Score `0.9683`,
|
||||
- masked 5%: Macro F1 `0.9829`, severity `0.9333`, Raw Score `0.9705`.
|
||||
|
||||
Pełny pakiet po iteracji: `32/32` testów oraz poprawny healthcheck Streamlit `ok`.
|
||||
Pełny pakiet po iteracji: `38/38` testów oraz poprawny test stanu Streamlit `ok`.
|
||||
|
||||
@ -7,7 +7,7 @@ from collections.abc import Sequence
|
||||
import numpy as np
|
||||
import plotly.graph_objects as go
|
||||
|
||||
from .config import FREQ_COLS, LABEL_COLORS
|
||||
from .config import FREQ_COLS, LABEL_COLORS, LABEL_DISPLAY, LABEL_ICONS
|
||||
from .explainability import EngineAnalysis
|
||||
|
||||
PLOT_BG = "#101820"
|
||||
@ -15,6 +15,7 @@ GRID = "rgba(148, 163, 184, 0.14)"
|
||||
TEXT = "#dce8ee"
|
||||
MUTED = "#8fa6b2"
|
||||
COMPARISON_COLORS = ("#28b7d9", "#f5a524", "#38d996", "#d56cff", "#2f78ed")
|
||||
HEATMAP_DEVIATION_LIMIT_MV = 20.0
|
||||
|
||||
|
||||
def _base_layout(
|
||||
@ -38,15 +39,18 @@ def _base_layout(
|
||||
|
||||
|
||||
def engine_heatmap(analysis: EngineAnalysis) -> go.Figure:
|
||||
cylinders = analysis.measurements["cylinder"].astype(int).tolist()
|
||||
max_abs = max(float(np.percentile(analysis.absolute_deviation, 95)), 1.0)
|
||||
row_labels = [
|
||||
f"{LABEL_ICONS[str(row.label)]} C{int(row.cylinder):02d} · "
|
||||
f"{LABEL_DISPLAY[str(row.label)]}"
|
||||
for row in analysis.diagnostics.itertuples()
|
||||
]
|
||||
fig = go.Figure(
|
||||
go.Heatmap(
|
||||
z=analysis.deviation,
|
||||
x=list(range(len(FREQ_COLS))),
|
||||
y=[f"C{value:02d}" for value in cylinders],
|
||||
zmin=-max_abs,
|
||||
zmax=max_abs,
|
||||
y=row_labels,
|
||||
zmin=-HEATMAP_DEVIATION_LIMIT_MV,
|
||||
zmax=HEATMAP_DEVIATION_LIMIT_MV,
|
||||
zmid=0,
|
||||
colorscale=[
|
||||
[0.0, "#28b7d9"],
|
||||
@ -54,10 +58,10 @@ def engine_heatmap(analysis: EngineAnalysis) -> go.Figure:
|
||||
[1.0, "#ff6b57"],
|
||||
],
|
||||
colorbar=dict(title="Δ mV", thickness=12),
|
||||
hovertemplate="Cylinder %{y}<br>%{x} kHz<br>Odchylenie %{z:.1f} mV<extra></extra>",
|
||||
hovertemplate="%{y}<br>%{x} kHz<br>Odchylenie %{z:.1f} mV<extra></extra>",
|
||||
)
|
||||
)
|
||||
fig.update_yaxes(autorange="reversed", title="Cylinder")
|
||||
fig.update_yaxes(autorange="reversed", automargin=True, title=None)
|
||||
fig.update_xaxes(title="Częstotliwość [kHz]", dtick=2)
|
||||
return _base_layout(fig, height=360, margin_top=18, margin_bottom=42)
|
||||
|
||||
|
||||
@ -12,15 +12,22 @@ class StreamlitSmokeTests(unittest.TestCase):
|
||||
def test_default_demo_renders_without_exception(self) -> None:
|
||||
app = AppTest.from_file(str(ROOT / "app.py"), default_timeout=30).run()
|
||||
self.assertEqual(len(app.exception), 0)
|
||||
self.assertGreaterEqual(len(app.button), 8)
|
||||
self.assertGreaterEqual(len(app.selectbox), 1)
|
||||
self.assertEqual(app.radio[0].value, "Dane demonstracyjne")
|
||||
self.assertEqual(app.segmented_control[0].value, "Przegląd")
|
||||
self.assertEqual(
|
||||
app.segmented_control[0].options,
|
||||
["Przegląd", "Szczegóły cylindra"],
|
||||
)
|
||||
self.assertFalse(any("C01" in button.label for button in app.button))
|
||||
self.assertFalse(
|
||||
any(selector.label == "Cylinder główny" for selector in app.selectbox)
|
||||
)
|
||||
rendered = "\n".join(markdown.value for markdown in app.markdown)
|
||||
self.assertIn("Konsola diagnostyczna ENGIN", rendered)
|
||||
self.assertIn("ARTEFAKT ZWERYFIKOWANY", rendered)
|
||||
self.assertIn("runtime-verified", rendered)
|
||||
self.assertNotIn("CPU · DANE LOKALNE", rendered)
|
||||
for english_fragment in (
|
||||
"Diagnostic Console",
|
||||
"LIVE INFERENCE",
|
||||
@ -31,7 +38,6 @@ class StreamlitSmokeTests(unittest.TestCase):
|
||||
):
|
||||
self.assertNotIn(english_fragment, rendered)
|
||||
|
||||
app.segmented_control[0].set_value("Model").run()
|
||||
self.assertEqual(
|
||||
[metric.label for metric in app.metric],
|
||||
[
|
||||
@ -41,7 +47,16 @@ class StreamlitSmokeTests(unittest.TestCase):
|
||||
"Testy",
|
||||
],
|
||||
)
|
||||
diagnostic_columns = set(app.dataframe[0].value.columns)
|
||||
technical_expander = next(
|
||||
expander for expander in app.expander if expander.label == "Informacje techniczne"
|
||||
)
|
||||
self.assertFalse(technical_expander.proto.expanded)
|
||||
diagnostic_table = next(
|
||||
table
|
||||
for table in app.dataframe
|
||||
if "Źródło decyzji" in table.value.columns
|
||||
)
|
||||
diagnostic_columns = set(diagnostic_table.value.columns)
|
||||
self.assertIn("Źródło decyzji", diagnostic_columns)
|
||||
self.assertNotIn("decision_source", diagnostic_columns)
|
||||
|
||||
@ -51,14 +66,20 @@ class StreamlitSmokeTests(unittest.TestCase):
|
||||
self.assertEqual(len(app.exception), 0)
|
||||
self.assertGreaterEqual(len(app.info), 1)
|
||||
|
||||
def test_cylinder_grid_and_detail_selector_share_state(self) -> None:
|
||||
def test_cylinder_selector_exists_only_in_detail_view(self) -> None:
|
||||
app = AppTest.from_file(str(ROOT / "app.py"), default_timeout=30).run()
|
||||
target = next(button for button in app.button if "C03" in button.label)
|
||||
target.click().run()
|
||||
self.assertFalse(
|
||||
any(selector.label == "Cylinder główny" for selector in app.selectbox)
|
||||
)
|
||||
app.segmented_control[0].set_value("Szczegóły cylindra").run()
|
||||
self.assertEqual(app.segmented_control[0].value, "Szczegóły cylindra")
|
||||
detail_selector = next(
|
||||
selector for selector in app.selectbox if selector.label == "Cylinder główny"
|
||||
)
|
||||
detail_selector.set_value(3).run()
|
||||
detail_selector = next(
|
||||
selector for selector in app.selectbox if selector.label == "Cylinder główny"
|
||||
)
|
||||
self.assertEqual(detail_selector.value, 3)
|
||||
comparison = app.multiselect[0]
|
||||
self.assertEqual(comparison.label, "Porównaj z cylindrami")
|
||||
|
||||
@ -66,6 +66,10 @@ class ExplainabilityTests(unittest.TestCase):
|
||||
heatmap = engine_heatmap(self.analysis)
|
||||
self.assertEqual(len(heatmap.data), 1)
|
||||
self.assertEqual(heatmap.layout.height, 360)
|
||||
self.assertEqual(heatmap.data[0].zmin, -20.0)
|
||||
self.assertEqual(heatmap.data[0].zmax, 20.0)
|
||||
self.assertTrue(any("Sprawny" in label for label in heatmap.data[0].y))
|
||||
self.assertTrue(any("·" in label for label in heatmap.data[0].y))
|
||||
self.assertEqual(len(cylinder_spectrum(self.analysis, cylinder).data), 2)
|
||||
self.assertEqual(
|
||||
len(cylinder_spectrum(self.analysis, cylinder, compared).data),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user