Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -29,8 +29,6 @@ COLORS = {
|
|
| 29 |
# Page / Theme
|
| 30 |
# =========================
|
| 31 |
st.set_page_config(page_title="ST_GeoMech_UCS", page_icon="logo.png", layout="wide")
|
| 32 |
-
|
| 33 |
-
# Hide Streamlit default header/footer and tighten layout
|
| 34 |
st.markdown("<style>header, footer{visibility:hidden !important;}</style>", unsafe_allow_html=True)
|
| 35 |
st.markdown(
|
| 36 |
"""
|
|
@@ -53,11 +51,10 @@ st.markdown(
|
|
| 53 |
# =========================
|
| 54 |
# Helpers
|
| 55 |
# =========================
|
| 56 |
-
|
| 57 |
-
# Prefer real dialog (Streamlit ≥1.31). Fallback to expander if older.
|
| 58 |
try:
|
| 59 |
dialog = st.dialog
|
| 60 |
except AttributeError:
|
|
|
|
| 61 |
def dialog(title):
|
| 62 |
def deco(fn):
|
| 63 |
def wrapper(*args, **kwargs):
|
|
@@ -102,31 +99,23 @@ def find_sheet(book, names):
|
|
| 102 |
return None
|
| 103 |
|
| 104 |
def cross_plot(actual, pred, title, size=(3.9, 3.9)):
|
| 105 |
-
"""Compact, square cross-plot with a 1:1 reference line."""
|
| 106 |
fig, ax = plt.subplots(figsize=size, dpi=100)
|
| 107 |
ax.scatter(actual, pred, s=14, alpha=0.85, color=COLORS["pred"])
|
| 108 |
lo = float(np.nanmin([actual.min(), pred.min()]))
|
| 109 |
hi = float(np.nanmax([actual.max(), pred.max()]))
|
| 110 |
pad = 0.03 * (hi - lo if hi > lo else 1.0)
|
| 111 |
-
ax.plot([lo - pad, hi + pad], [lo - pad, hi + pad],
|
| 112 |
-
|
| 113 |
-
ax.
|
| 114 |
-
ax.set_ylim(lo - pad, hi + pad)
|
| 115 |
-
ax.set_aspect('equal', 'box') # perfect 1:1
|
| 116 |
ax.set_xlabel("Actual UCS"); ax.set_ylabel("Predicted UCS"); ax.set_title(title)
|
| 117 |
ax.grid(True, ls=":", alpha=0.4)
|
| 118 |
return fig
|
| 119 |
|
| 120 |
def depth_or_index_track(df, title=None, include_actual=True):
|
| 121 |
-
"""
|
| 122 |
-
Narrow, tall track: predicted solid blue; actual dotted yellow.
|
| 123 |
-
Works for either Depth on Y or Index on Y.
|
| 124 |
-
"""
|
| 125 |
depth_col = next((c for c in df.columns if 'depth' in str(c).lower()), None)
|
| 126 |
fig_w = 3.1
|
| 127 |
fig_h = 7.6 if depth_col is not None else 7.2
|
| 128 |
fig, ax = plt.subplots(figsize=(fig_w, fig_h), dpi=100)
|
| 129 |
-
|
| 130 |
if depth_col is not None:
|
| 131 |
ax.plot(df["UCS_Pred"], df[depth_col], '-', lw=1.8, color=COLORS["pred"], label="UCS_Pred")
|
| 132 |
if include_actual and TARGET in df.columns:
|
|
@@ -140,7 +129,6 @@ def depth_or_index_track(df, title=None, include_actual=True):
|
|
| 140 |
ax.plot(df[TARGET], idx, ':', lw=2.0, color=COLORS["actual"], alpha=0.95, label="UCS (actual)")
|
| 141 |
ax.set_ylabel("Point Index"); ax.set_xlabel("UCS")
|
| 142 |
ax.xaxis.set_label_position('top'); ax.xaxis.tick_top(); ax.invert_yaxis()
|
| 143 |
-
|
| 144 |
ax.grid(True, linestyle=":", alpha=0.4)
|
| 145 |
if title: ax.set_title(title, pad=8)
|
| 146 |
ax.legend(loc="best")
|
|
@@ -182,9 +170,7 @@ def inline_logo(path="logo.png") -> str:
|
|
| 182 |
return ""
|
| 183 |
|
| 184 |
# ---------- Preview modal helpers ----------
|
| 185 |
-
|
| 186 |
def make_index_tracks(df: pd.DataFrame, cols: list[str]):
|
| 187 |
-
"""Build a side-by-side index track figure (log-style) for given columns."""
|
| 188 |
cols = [c for c in cols if c in df.columns]
|
| 189 |
n = len(cols)
|
| 190 |
if n == 0:
|
|
@@ -192,14 +178,13 @@ def make_index_tracks(df: pd.DataFrame, cols: list[str]):
|
|
| 192 |
ax.text(0.5, 0.5, "No selected columns in sheet", ha="center", va="center")
|
| 193 |
ax.axis("off")
|
| 194 |
return fig
|
| 195 |
-
width_per = 2.2
|
| 196 |
fig_h = 7.0
|
| 197 |
fig, axes = plt.subplots(1, n, figsize=(width_per * n, fig_h), sharey=True, dpi=100)
|
| 198 |
-
if n == 1:
|
| 199 |
-
axes = [axes]
|
| 200 |
idx = np.arange(1, len(df) + 1)
|
| 201 |
for ax, col in zip(axes, cols):
|
| 202 |
-
ax.plot(df[col], idx, '-', lw=1.4, color="#
|
| 203 |
ax.set_xlabel(col)
|
| 204 |
ax.xaxis.set_label_position('top'); ax.xaxis.tick_top(); ax.invert_yaxis()
|
| 205 |
ax.grid(True, linestyle=":", alpha=0.3)
|
|
@@ -212,47 +197,37 @@ def stats_table(df: pd.DataFrame, cols: list[str]) -> pd.DataFrame:
|
|
| 212 |
return pd.DataFrame({"Feature": [], "Min": [], "Max": [], "Mean": [], "Std": []})
|
| 213 |
out = df[cols].agg(['min', 'max', 'mean', 'std']).T
|
| 214 |
out = out.rename(columns={"min": "Min", "max": "Max", "mean": "Mean", "std": "Std"})
|
| 215 |
-
|
| 216 |
-
return out
|
| 217 |
|
| 218 |
@dialog("Preview data")
|
| 219 |
def preview_modal_dev(book: dict[str, pd.DataFrame], feature_cols: list[str]):
|
| 220 |
if not book:
|
| 221 |
-
st.info("No data loaded yet.")
|
| 222 |
-
return
|
| 223 |
-
# Auto detect Train/Test-like sheets
|
| 224 |
sh_train = find_sheet(book, ["Train","Training","training2","train","training"])
|
| 225 |
sh_test = find_sheet(book, ["Test","Testing","testing2","test","testing"])
|
| 226 |
-
tabs = []
|
| 227 |
-
data = []
|
| 228 |
if sh_train: tabs.append("Train"); data.append(book[sh_train])
|
| 229 |
if sh_test: tabs.append("Test"); data.append(book[sh_test])
|
| 230 |
-
if not tabs:
|
| 231 |
first_name = list(book.keys())[0]
|
| 232 |
tabs = [first_name]; data = [book[first_name]]
|
| 233 |
-
|
| 234 |
st.write("Use the tabs to switch between Train/Test views (if available).")
|
| 235 |
t_objs = st.tabs(tabs)
|
| 236 |
for t, df in zip(t_objs, data):
|
| 237 |
with t:
|
| 238 |
-
|
| 239 |
-
with
|
| 240 |
-
|
| 241 |
-
with tab_summary:
|
| 242 |
-
st.dataframe(stats_table(df, feature_cols), use_container_width=True)
|
| 243 |
|
| 244 |
@dialog("Preview data")
|
| 245 |
def preview_modal_val(book: dict[str, pd.DataFrame], feature_cols: list[str]):
|
| 246 |
if not book:
|
| 247 |
-
st.info("No data loaded yet.")
|
| 248 |
-
return
|
| 249 |
vname = find_sheet(book, ["Validation","Validate","validation2","Val","val"]) or list(book.keys())[0]
|
| 250 |
df = book[vname]
|
| 251 |
-
|
| 252 |
-
with
|
| 253 |
-
|
| 254 |
-
with tab_summary:
|
| 255 |
-
st.dataframe(stats_table(df, feature_cols), use_container_width=True)
|
| 256 |
|
| 257 |
# =========================
|
| 258 |
# Model presence (local or optional download)
|
|
@@ -308,11 +283,14 @@ if "app_step" not in st.session_state: st.session_state.app_step = "intro"
|
|
| 308 |
if "results" not in st.session_state: st.session_state.results = {}
|
| 309 |
if "train_ranges" not in st.session_state: st.session_state.train_ranges = None
|
| 310 |
if "dev_ready" not in st.session_state: st.session_state.dev_ready = False
|
|
|
|
|
|
|
|
|
|
| 311 |
if ("Train" in st.session_state.results) or ("Test" in st.session_state.results):
|
| 312 |
st.session_state.dev_ready = True
|
| 313 |
|
| 314 |
# =========================
|
| 315 |
-
# Hero header (logo + title)
|
| 316 |
# =========================
|
| 317 |
st.markdown(
|
| 318 |
f"""
|
|
@@ -345,9 +323,10 @@ if st.session_state.app_step == "intro":
|
|
| 345 |
)
|
| 346 |
st.subheader("How It Works")
|
| 347 |
st.markdown(
|
| 348 |
-
"1. **Upload your
|
| 349 |
-
"2. Click **
|
| 350 |
-
"3.
|
|
|
|
| 351 |
)
|
| 352 |
if st.button("Start Showcase", type="primary", key="start_showcase"):
|
| 353 |
st.session_state.app_step = "dev"; st.rerun()
|
|
@@ -357,27 +336,32 @@ if st.session_state.app_step == "intro":
|
|
| 357 |
# =========================
|
| 358 |
if st.session_state.app_step == "dev":
|
| 359 |
st.sidebar.header("Model Development Data")
|
| 360 |
-
|
| 361 |
-
if st.session_state.get("dev_file_name")
|
| 362 |
-
dev_label = "Replace data (Excel)"
|
| 363 |
train_test_file = st.sidebar.file_uploader(dev_label, type=["xlsx","xls"], key="dev_upload")
|
|
|
|
|
|
|
| 364 |
if train_test_file is not None:
|
| 365 |
st.session_state.dev_file_name = train_test_file.name
|
| 366 |
-
# small status line in sidebar
|
| 367 |
_book_tmp = read_book(train_test_file)
|
| 368 |
if _book_tmp:
|
| 369 |
first_df = next(iter(_book_tmp.values()))
|
| 370 |
st.sidebar.caption(f"**Data loaded:** {train_test_file.name} • {first_df.shape[0]} rows × {first_df.shape[1]} cols")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
|
| 372 |
# Preview button (modal)
|
| 373 |
-
preview_btn = st.sidebar.button("Preview data", use_container_width=True, disabled=
|
| 374 |
-
if preview_btn and train_test_file is not None:
|
| 375 |
_book = read_book(train_test_file)
|
|
|
|
| 376 |
preview_modal_dev(_book, FEATURES)
|
| 377 |
|
| 378 |
run_btn = st.sidebar.button("Run Model", type="primary", use_container_width=True)
|
| 379 |
|
| 380 |
-
# Proceed button BELOW run, always
|
| 381 |
st.sidebar.button(
|
| 382 |
"Proceed to Prediction ▶",
|
| 383 |
use_container_width=True,
|
|
@@ -385,9 +369,16 @@ if st.session_state.app_step == "dev":
|
|
| 385 |
on_click=(lambda: st.session_state.update(app_step="predict")) if st.session_state.dev_ready else None,
|
| 386 |
)
|
| 387 |
|
| 388 |
-
#
|
| 389 |
st.subheader("Model Development")
|
| 390 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 391 |
|
| 392 |
if run_btn and train_test_file is not None:
|
| 393 |
with st.status("Processing…", expanded=False) as status:
|
|
@@ -427,8 +418,9 @@ if st.session_state.app_step == "dev":
|
|
| 427 |
|
| 428 |
st.session_state.dev_ready = True # enable Proceed button immediately
|
| 429 |
status.update(label="Done ✓", state="complete"); toast("Model run complete 🚀")
|
| 430 |
-
st.rerun() # refresh to
|
| 431 |
|
|
|
|
| 432 |
if ("Train" in st.session_state.results) or ("Test" in st.session_state.results):
|
| 433 |
tab1, tab2 = st.tabs(["Training", "Testing"])
|
| 434 |
if "Train" in st.session_state.results:
|
|
@@ -436,12 +428,11 @@ if st.session_state.app_step == "dev":
|
|
| 436 |
df = st.session_state.results["Train"]; m = st.session_state.results["metrics_train"]
|
| 437 |
c1,c2,c3 = st.columns(3)
|
| 438 |
c1.metric("R²", f"{m['R2']:.4f}"); c2.metric("RMSE", f"{m['RMSE']:.4f}"); c3.metric("MAE", f"{m['MAE']:.4f}")
|
| 439 |
-
left, right = st.columns([0.9, 0.55])
|
| 440 |
with left:
|
| 441 |
st.pyplot(cross_plot(df[TARGET], df["UCS_Pred"], "Training: Actual vs Predicted"), use_container_width=True)
|
| 442 |
with right:
|
| 443 |
st.pyplot(depth_or_index_track(df, title=None, include_actual=True), use_container_width=True)
|
| 444 |
-
|
| 445 |
if "Test" in st.session_state.results:
|
| 446 |
with tab2:
|
| 447 |
df = st.session_state.results["Test"]; m = st.session_state.results["metrics_test"]
|
|
@@ -475,9 +466,7 @@ if st.session_state.app_step == "dev":
|
|
| 475 |
# =========================
|
| 476 |
if st.session_state.app_step == "predict":
|
| 477 |
st.sidebar.header("Prediction (Validation)")
|
| 478 |
-
val_label = "Upload Validation Excel"
|
| 479 |
-
if st.session_state.get("val_file_name"):
|
| 480 |
-
val_label = "Replace data (Excel)"
|
| 481 |
validation_file = st.sidebar.file_uploader(val_label, type=["xlsx","xls"], key="val_upload")
|
| 482 |
if validation_file is not None:
|
| 483 |
st.session_state.val_file_name = validation_file.name
|
|
@@ -486,7 +475,6 @@ if st.session_state.app_step == "predict":
|
|
| 486 |
first_df = next(iter(_book_tmp.values()))
|
| 487 |
st.sidebar.caption(f"**Data loaded:** {validation_file.name} • {first_df.shape[0]} rows × {first_df.shape[1]} cols")
|
| 488 |
|
| 489 |
-
# Preview button for validation data
|
| 490 |
preview_val_btn = st.sidebar.button("Preview data", use_container_width=True, disabled=(validation_file is None))
|
| 491 |
if preview_val_btn and validation_file is not None:
|
| 492 |
_book = read_book(validation_file)
|
|
@@ -547,17 +535,13 @@ if st.session_state.app_step == "predict":
|
|
| 547 |
c1.metric("points", f"{sv['n_points']}"); c2.metric("Pred min", f"{sv['pred_min']:.2f}")
|
| 548 |
c3.metric("Pred max", f"{sv['pred_max']:.2f}"); c4.metric("OOR %", f"{sv['oor_pct']:.1f}%")
|
| 549 |
|
| 550 |
-
left, right = st.columns([0.9, 0.55])
|
| 551 |
with left:
|
| 552 |
if TARGET in st.session_state.results["Validate"].columns:
|
| 553 |
-
st.pyplot(
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
"Validation: Actual vs Predicted"
|
| 558 |
-
),
|
| 559 |
-
use_container_width=True
|
| 560 |
-
)
|
| 561 |
else:
|
| 562 |
st.info("Actual UCS values are not available in the validation data. Cross-plot cannot be generated.")
|
| 563 |
with right:
|
|
|
|
| 29 |
# Page / Theme
|
| 30 |
# =========================
|
| 31 |
st.set_page_config(page_title="ST_GeoMech_UCS", page_icon="logo.png", layout="wide")
|
|
|
|
|
|
|
| 32 |
st.markdown("<style>header, footer{visibility:hidden !important;}</style>", unsafe_allow_html=True)
|
| 33 |
st.markdown(
|
| 34 |
"""
|
|
|
|
| 51 |
# =========================
|
| 52 |
# Helpers
|
| 53 |
# =========================
|
|
|
|
|
|
|
| 54 |
try:
|
| 55 |
dialog = st.dialog
|
| 56 |
except AttributeError:
|
| 57 |
+
# Back-compat shim if st.dialog isn't available
|
| 58 |
def dialog(title):
|
| 59 |
def deco(fn):
|
| 60 |
def wrapper(*args, **kwargs):
|
|
|
|
| 99 |
return None
|
| 100 |
|
| 101 |
def cross_plot(actual, pred, title, size=(3.9, 3.9)):
|
|
|
|
| 102 |
fig, ax = plt.subplots(figsize=size, dpi=100)
|
| 103 |
ax.scatter(actual, pred, s=14, alpha=0.85, color=COLORS["pred"])
|
| 104 |
lo = float(np.nanmin([actual.min(), pred.min()]))
|
| 105 |
hi = float(np.nanmax([actual.max(), pred.max()]))
|
| 106 |
pad = 0.03 * (hi - lo if hi > lo else 1.0)
|
| 107 |
+
ax.plot([lo - pad, hi + pad], [lo - pad, hi + pad], '--', lw=1.2, color=COLORS["ref"])
|
| 108 |
+
ax.set_xlim(lo - pad, hi + pad); ax.set_ylim(lo - pad, hi + pad)
|
| 109 |
+
ax.set_aspect('equal', 'box')
|
|
|
|
|
|
|
| 110 |
ax.set_xlabel("Actual UCS"); ax.set_ylabel("Predicted UCS"); ax.set_title(title)
|
| 111 |
ax.grid(True, ls=":", alpha=0.4)
|
| 112 |
return fig
|
| 113 |
|
| 114 |
def depth_or_index_track(df, title=None, include_actual=True):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
depth_col = next((c for c in df.columns if 'depth' in str(c).lower()), None)
|
| 116 |
fig_w = 3.1
|
| 117 |
fig_h = 7.6 if depth_col is not None else 7.2
|
| 118 |
fig, ax = plt.subplots(figsize=(fig_w, fig_h), dpi=100)
|
|
|
|
| 119 |
if depth_col is not None:
|
| 120 |
ax.plot(df["UCS_Pred"], df[depth_col], '-', lw=1.8, color=COLORS["pred"], label="UCS_Pred")
|
| 121 |
if include_actual and TARGET in df.columns:
|
|
|
|
| 129 |
ax.plot(df[TARGET], idx, ':', lw=2.0, color=COLORS["actual"], alpha=0.95, label="UCS (actual)")
|
| 130 |
ax.set_ylabel("Point Index"); ax.set_xlabel("UCS")
|
| 131 |
ax.xaxis.set_label_position('top'); ax.xaxis.tick_top(); ax.invert_yaxis()
|
|
|
|
| 132 |
ax.grid(True, linestyle=":", alpha=0.4)
|
| 133 |
if title: ax.set_title(title, pad=8)
|
| 134 |
ax.legend(loc="best")
|
|
|
|
| 170 |
return ""
|
| 171 |
|
| 172 |
# ---------- Preview modal helpers ----------
|
|
|
|
| 173 |
def make_index_tracks(df: pd.DataFrame, cols: list[str]):
|
|
|
|
| 174 |
cols = [c for c in cols if c in df.columns]
|
| 175 |
n = len(cols)
|
| 176 |
if n == 0:
|
|
|
|
| 178 |
ax.text(0.5, 0.5, "No selected columns in sheet", ha="center", va="center")
|
| 179 |
ax.axis("off")
|
| 180 |
return fig
|
| 181 |
+
width_per = 2.2
|
| 182 |
fig_h = 7.0
|
| 183 |
fig, axes = plt.subplots(1, n, figsize=(width_per * n, fig_h), sharey=True, dpi=100)
|
| 184 |
+
if n == 1: axes = [axes]
|
|
|
|
| 185 |
idx = np.arange(1, len(df) + 1)
|
| 186 |
for ax, col in zip(axes, cols):
|
| 187 |
+
ax.plot(df[col], idx, '-', lw=1.4, color="#333")
|
| 188 |
ax.set_xlabel(col)
|
| 189 |
ax.xaxis.set_label_position('top'); ax.xaxis.tick_top(); ax.invert_yaxis()
|
| 190 |
ax.grid(True, linestyle=":", alpha=0.3)
|
|
|
|
| 197 |
return pd.DataFrame({"Feature": [], "Min": [], "Max": [], "Mean": [], "Std": []})
|
| 198 |
out = df[cols].agg(['min', 'max', 'mean', 'std']).T
|
| 199 |
out = out.rename(columns={"min": "Min", "max": "Max", "mean": "Mean", "std": "Std"})
|
| 200 |
+
return out.reset_index().rename(columns={"index": "Feature"})
|
|
|
|
| 201 |
|
| 202 |
@dialog("Preview data")
|
| 203 |
def preview_modal_dev(book: dict[str, pd.DataFrame], feature_cols: list[str]):
|
| 204 |
if not book:
|
| 205 |
+
st.info("No data loaded yet."); return
|
|
|
|
|
|
|
| 206 |
sh_train = find_sheet(book, ["Train","Training","training2","train","training"])
|
| 207 |
sh_test = find_sheet(book, ["Test","Testing","testing2","test","testing"])
|
| 208 |
+
tabs, data = [], []
|
|
|
|
| 209 |
if sh_train: tabs.append("Train"); data.append(book[sh_train])
|
| 210 |
if sh_test: tabs.append("Test"); data.append(book[sh_test])
|
| 211 |
+
if not tabs:
|
| 212 |
first_name = list(book.keys())[0]
|
| 213 |
tabs = [first_name]; data = [book[first_name]]
|
|
|
|
| 214 |
st.write("Use the tabs to switch between Train/Test views (if available).")
|
| 215 |
t_objs = st.tabs(tabs)
|
| 216 |
for t, df in zip(t_objs, data):
|
| 217 |
with t:
|
| 218 |
+
t1, t2 = st.tabs(["Tracks", "Summary"])
|
| 219 |
+
with t1: st.pyplot(make_index_tracks(df, feature_cols), use_container_width=True)
|
| 220 |
+
with t2: st.dataframe(stats_table(df, feature_cols), use_container_width=True)
|
|
|
|
|
|
|
| 221 |
|
| 222 |
@dialog("Preview data")
|
| 223 |
def preview_modal_val(book: dict[str, pd.DataFrame], feature_cols: list[str]):
|
| 224 |
if not book:
|
| 225 |
+
st.info("No data loaded yet."); return
|
|
|
|
| 226 |
vname = find_sheet(book, ["Validation","Validate","validation2","Val","val"]) or list(book.keys())[0]
|
| 227 |
df = book[vname]
|
| 228 |
+
t1, t2 = st.tabs(["Tracks", "Summary"])
|
| 229 |
+
with t1: st.pyplot(make_index_tracks(df, feature_cols), use_container_width=True)
|
| 230 |
+
with t2: st.dataframe(stats_table(df, feature_cols), use_container_width=True)
|
|
|
|
|
|
|
| 231 |
|
| 232 |
# =========================
|
| 233 |
# Model presence (local or optional download)
|
|
|
|
| 283 |
if "results" not in st.session_state: st.session_state.results = {}
|
| 284 |
if "train_ranges" not in st.session_state: st.session_state.train_ranges = None
|
| 285 |
if "dev_ready" not in st.session_state: st.session_state.dev_ready = False
|
| 286 |
+
if "dev_file_loaded" not in st.session_state: st.session_state.dev_file_loaded = False
|
| 287 |
+
if "dev_previewed" not in st.session_state: st.session_state.dev_previewed = False
|
| 288 |
+
|
| 289 |
if ("Train" in st.session_state.results) or ("Test" in st.session_state.results):
|
| 290 |
st.session_state.dev_ready = True
|
| 291 |
|
| 292 |
# =========================
|
| 293 |
+
# Hero header (logo + title)
|
| 294 |
# =========================
|
| 295 |
st.markdown(
|
| 296 |
f"""
|
|
|
|
| 323 |
)
|
| 324 |
st.subheader("How It Works")
|
| 325 |
st.markdown(
|
| 326 |
+
"1. **Upload your data to build the case and preview the performance of our model.** \n"
|
| 327 |
+
"2. Click **Run Model** to compute metrics and plots. \n"
|
| 328 |
+
"3. Click **Proceed to Prediction** to validate on a new dataset. \n"
|
| 329 |
+
"4. Export results to Excel at any time."
|
| 330 |
)
|
| 331 |
if st.button("Start Showcase", type="primary", key="start_showcase"):
|
| 332 |
st.session_state.app_step = "dev"; st.rerun()
|
|
|
|
| 336 |
# =========================
|
| 337 |
if st.session_state.app_step == "dev":
|
| 338 |
st.sidebar.header("Model Development Data")
|
| 339 |
+
|
| 340 |
+
dev_label = "Upload Data (Excel)" if not st.session_state.get("dev_file_name") else "Replace data (Excel)"
|
|
|
|
| 341 |
train_test_file = st.sidebar.file_uploader(dev_label, type=["xlsx","xls"], key="dev_upload")
|
| 342 |
+
|
| 343 |
+
# If a file is uploaded, parse once and set flags
|
| 344 |
if train_test_file is not None:
|
| 345 |
st.session_state.dev_file_name = train_test_file.name
|
|
|
|
| 346 |
_book_tmp = read_book(train_test_file)
|
| 347 |
if _book_tmp:
|
| 348 |
first_df = next(iter(_book_tmp.values()))
|
| 349 |
st.sidebar.caption(f"**Data loaded:** {train_test_file.name} • {first_df.shape[0]} rows × {first_df.shape[1]} cols")
|
| 350 |
+
st.session_state.dev_file_loaded = True
|
| 351 |
+
# Reset preview + readiness on new upload
|
| 352 |
+
st.session_state.dev_previewed = False
|
| 353 |
+
st.session_state.dev_ready = False
|
| 354 |
|
| 355 |
# Preview button (modal)
|
| 356 |
+
preview_btn = st.sidebar.button("Preview data", use_container_width=True, disabled=not st.session_state.dev_file_loaded)
|
| 357 |
+
if preview_btn and st.session_state.dev_file_loaded and train_test_file is not None:
|
| 358 |
_book = read_book(train_test_file)
|
| 359 |
+
st.session_state.dev_previewed = True # mark previewed
|
| 360 |
preview_modal_dev(_book, FEATURES)
|
| 361 |
|
| 362 |
run_btn = st.sidebar.button("Run Model", type="primary", use_container_width=True)
|
| 363 |
|
| 364 |
+
# Proceed button BELOW run, visible always; enables after first successful run
|
| 365 |
st.sidebar.button(
|
| 366 |
"Proceed to Prediction ▶",
|
| 367 |
use_container_width=True,
|
|
|
|
| 369 |
on_click=(lambda: st.session_state.update(app_step="predict")) if st.session_state.dev_ready else None,
|
| 370 |
)
|
| 371 |
|
| 372 |
+
# ----- Title & helper (ALWAYS visible; state-aware) -----
|
| 373 |
st.subheader("Model Development")
|
| 374 |
+
if st.session_state.dev_ready:
|
| 375 |
+
st.success("Case has been built and results are displayed below.")
|
| 376 |
+
elif st.session_state.dev_file_loaded and st.session_state.dev_previewed:
|
| 377 |
+
st.info("Previewed ✓ — now click **Run Model** to build the case.")
|
| 378 |
+
elif st.session_state.dev_file_loaded:
|
| 379 |
+
st.info("📄 **Preview uploaded data** using the sidebar button, then click **Run Model**.")
|
| 380 |
+
else:
|
| 381 |
+
st.write("**Upload your data to build a case, then run the model to review development performance.**")
|
| 382 |
|
| 383 |
if run_btn and train_test_file is not None:
|
| 384 |
with st.status("Processing…", expanded=False) as status:
|
|
|
|
| 418 |
|
| 419 |
st.session_state.dev_ready = True # enable Proceed button immediately
|
| 420 |
status.update(label="Done ✓", state="complete"); toast("Model run complete 🚀")
|
| 421 |
+
st.rerun() # refresh UI to reflect new helper & enabled button
|
| 422 |
|
| 423 |
+
# Results
|
| 424 |
if ("Train" in st.session_state.results) or ("Test" in st.session_state.results):
|
| 425 |
tab1, tab2 = st.tabs(["Training", "Testing"])
|
| 426 |
if "Train" in st.session_state.results:
|
|
|
|
| 428 |
df = st.session_state.results["Train"]; m = st.session_state.results["metrics_train"]
|
| 429 |
c1,c2,c3 = st.columns(3)
|
| 430 |
c1.metric("R²", f"{m['R2']:.4f}"); c2.metric("RMSE", f"{m['RMSE']:.4f}"); c3.metric("MAE", f"{m['MAE']:.4f}")
|
| 431 |
+
left, right = st.columns([0.9, 0.55])
|
| 432 |
with left:
|
| 433 |
st.pyplot(cross_plot(df[TARGET], df["UCS_Pred"], "Training: Actual vs Predicted"), use_container_width=True)
|
| 434 |
with right:
|
| 435 |
st.pyplot(depth_or_index_track(df, title=None, include_actual=True), use_container_width=True)
|
|
|
|
| 436 |
if "Test" in st.session_state.results:
|
| 437 |
with tab2:
|
| 438 |
df = st.session_state.results["Test"]; m = st.session_state.results["metrics_test"]
|
|
|
|
| 466 |
# =========================
|
| 467 |
if st.session_state.app_step == "predict":
|
| 468 |
st.sidebar.header("Prediction (Validation)")
|
| 469 |
+
val_label = "Upload Validation Excel" if not st.session_state.get("val_file_name") else "Replace data (Excel)"
|
|
|
|
|
|
|
| 470 |
validation_file = st.sidebar.file_uploader(val_label, type=["xlsx","xls"], key="val_upload")
|
| 471 |
if validation_file is not None:
|
| 472 |
st.session_state.val_file_name = validation_file.name
|
|
|
|
| 475 |
first_df = next(iter(_book_tmp.values()))
|
| 476 |
st.sidebar.caption(f"**Data loaded:** {validation_file.name} • {first_df.shape[0]} rows × {first_df.shape[1]} cols")
|
| 477 |
|
|
|
|
| 478 |
preview_val_btn = st.sidebar.button("Preview data", use_container_width=True, disabled=(validation_file is None))
|
| 479 |
if preview_val_btn and validation_file is not None:
|
| 480 |
_book = read_book(validation_file)
|
|
|
|
| 535 |
c1.metric("points", f"{sv['n_points']}"); c2.metric("Pred min", f"{sv['pred_min']:.2f}")
|
| 536 |
c3.metric("Pred max", f"{sv['pred_max']:.2f}"); c4.metric("OOR %", f"{sv['oor_pct']:.1f}%")
|
| 537 |
|
| 538 |
+
left, right = st.columns([0.9, 0.55])
|
| 539 |
with left:
|
| 540 |
if TARGET in st.session_state.results["Validate"].columns:
|
| 541 |
+
st.pyplot(cross_plot(st.session_state.results["Validate"][TARGET],
|
| 542 |
+
st.session_state.results["Validate"]["UCS_Pred"],
|
| 543 |
+
"Validation: Actual vs Predicted"),
|
| 544 |
+
use_container_width=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 545 |
else:
|
| 546 |
st.info("Actual UCS values are not available in the validation data. Cross-plot cannot be generated.")
|
| 547 |
with right:
|