feat(frontend): drift metric line + last-prediction session state
Browse files- Renders one-line drift caption between the calibration caption and
the SHAP section. Three states: warming up (<10 samples), unavailable
(no train stats), drift z-score with magnitude tag (in-band / mild /
significant).
- Stashes /predict/bbb response in st.session_state["last_bbb_prediction"]
so the Day-7 T3C AI Assistant tab can pick it up.
- No backend / schema / test count changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- src/frontend/app.py +24 -0
src/frontend/app.py
CHANGED
|
@@ -456,6 +456,7 @@ def _render_mri_tab() -> None:
|
|
| 456 |
|
| 457 |
def _render_prediction_card(result: dict) -> None:
|
| 458 |
"""Render a B2B-styled decision card: label badge + confidence + SHAP bars."""
|
|
|
|
| 459 |
label_text = _html.escape(str(result["label_text"]))
|
| 460 |
badge_color = "#166534" if result["label"] == 1 else "#991B1B"
|
| 461 |
badge_bg = "#DCFCE7" if result["label"] == 1 else "#FEE2E2"
|
|
@@ -509,6 +510,29 @@ def _render_prediction_card(result: dict) -> None:
|
|
| 509 |
f"precision'ı **{precision_pct}%** (n={support})."
|
| 510 |
)
|
| 511 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 512 |
# SHAP attributions chart
|
| 513 |
n_features = len(result["top_features"])
|
| 514 |
st.markdown(
|
|
|
|
| 456 |
|
| 457 |
def _render_prediction_card(result: dict) -> None:
|
| 458 |
"""Render a B2B-styled decision card: label badge + confidence + SHAP bars."""
|
| 459 |
+
st.session_state["last_bbb_prediction"] = result
|
| 460 |
label_text = _html.escape(str(result["label_text"]))
|
| 461 |
badge_color = "#166534" if result["label"] == 1 else "#991B1B"
|
| 462 |
badge_bg = "#DCFCE7" if result["label"] == 1 else "#FEE2E2"
|
|
|
|
| 510 |
f"precision'ı **{precision_pct}%** (n={support})."
|
| 511 |
)
|
| 512 |
|
| 513 |
+
drift_z = result.get("drift_z")
|
| 514 |
+
rolling_n = result.get("rolling_n", 0)
|
| 515 |
+
if drift_z is None and rolling_n < 10:
|
| 516 |
+
st.caption(
|
| 517 |
+
f"📈 Drift: warming up ({rolling_n}/10 predictions buffered)."
|
| 518 |
+
)
|
| 519 |
+
elif drift_z is None:
|
| 520 |
+
st.caption(
|
| 521 |
+
"📈 Drift: unavailable (model lacks train-time confidence stats)."
|
| 522 |
+
)
|
| 523 |
+
else:
|
| 524 |
+
# Sign + magnitude: |z| < 1 in-band, 1–2 mild, >=2 significant.
|
| 525 |
+
if abs(drift_z) < 1.0:
|
| 526 |
+
tag = "within expected range"
|
| 527 |
+
elif abs(drift_z) < 2.0:
|
| 528 |
+
tag = "mild distribution shift"
|
| 529 |
+
else:
|
| 530 |
+
tag = "significant shift — retrain recommended"
|
| 531 |
+
st.caption(
|
| 532 |
+
f"📈 Drift: trailing-{rolling_n} confidence median is "
|
| 533 |
+
f"**{drift_z:+.2f}σ** from train-time distribution ({tag})."
|
| 534 |
+
)
|
| 535 |
+
|
| 536 |
# SHAP attributions chart
|
| 537 |
n_features = len(result["top_features"])
|
| 538 |
st.markdown(
|