Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -22,9 +22,7 @@ TARGET = "UCS"
|
|
| 22 |
MODELS_DIR = Path("models")
|
| 23 |
DEFAULT_MODEL = MODELS_DIR / "ucs_rf.joblib"
|
| 24 |
MODEL_FALLBACKS = [MODELS_DIR / "model.joblib", MODELS_DIR / "model.pkl"]
|
| 25 |
-
|
| 26 |
-
# Updated colors for a dark blue theme
|
| 27 |
-
COLORS = {"pred": "#4A90E2", "actual": "#FFC107", "ref": "#B0B0B0"}
|
| 28 |
|
| 29 |
# ---- Plot sizing controls ----
|
| 30 |
CROSS_W = 450 # px (matplotlib figure size; Streamlit will still scale)
|
|
@@ -38,44 +36,39 @@ BOLD_FONT = "Arial Black, Arial, sans-serif" # used for bold axis titles & tick
|
|
| 38 |
# =========================
|
| 39 |
st.set_page_config(page_title="ST_GeoMech_UCS", page_icon="logo.png", layout="wide")
|
| 40 |
|
| 41 |
-
# This is the new CSS block
|
| 42 |
st.markdown("""
|
| 43 |
<style>
|
| 44 |
-
/*
|
| 45 |
-
.
|
| 46 |
-
color: #
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
| 49 |
}
|
| 50 |
|
| 51 |
-
/*
|
| 52 |
.fixed-header-container {
|
| 53 |
position: sticky;
|
| 54 |
top: 0;
|
| 55 |
z-index: 1000;
|
| 56 |
-
background-color: #
|
| 57 |
padding: 10px 0;
|
| 58 |
-
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.
|
| 59 |
-
}
|
| 60 |
-
|
| 61 |
-
/* Changed to a light gray/blue to be visible on the dark background */
|
| 62 |
-
.info-box {
|
| 63 |
-
background-color: #172a45;
|
| 64 |
-
color: #b1c7e0;
|
| 65 |
-
padding: 10px;
|
| 66 |
-
border-radius: 5px;
|
| 67 |
-
margin-bottom: 1rem;
|
| 68 |
}
|
| 69 |
|
|
|
|
| 70 |
.main .block-container {
|
| 71 |
-
padding-top: 250px;
|
| 72 |
}
|
| 73 |
|
| 74 |
/* General CSS */
|
| 75 |
.brand-logo { width: 16px; height: auto; object-fit: contain; }
|
| 76 |
.sidebar-header { display:flex; align-items:center; gap:12px; }
|
| 77 |
.sidebar-header .text h1 { font-size: 1.05rem; margin:0; line-height:1.1; }
|
| 78 |
-
.sidebar-header .text .tag { font-size: .85rem; color:#
|
| 79 |
.centered-container {
|
| 80 |
display: flex;
|
| 81 |
flex-direction: column;
|
|
@@ -85,24 +78,37 @@ st.markdown("""
|
|
| 85 |
</style>
|
| 86 |
""", unsafe_allow_html=True)
|
| 87 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
# Make the Preview expander title & tabs sticky (pinned to the top)
|
| 89 |
st.markdown("""
|
| 90 |
<style>
|
| 91 |
-
/* Updated to a dark background for consistency */
|
| 92 |
div[data-testid="stExpander"] > details > summary {
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
}
|
| 99 |
-
/* Updated to a dark background for consistency */
|
| 100 |
div[data-testid="stExpander"] div[data-baseweb="tab-list"] {
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
}
|
| 107 |
</style>
|
| 108 |
""", unsafe_allow_html=True)
|
|
@@ -140,8 +146,8 @@ def add_password_gate() -> None:
|
|
| 140 |
st.sidebar.markdown(f"""
|
| 141 |
<div class="centered-container">
|
| 142 |
<img src="{inline_logo('logo.png')}" style="width: 200px; height: auto; object-fit: contain;">
|
| 143 |
-
<div
|
| 144 |
-
<div style='color:#
|
| 145 |
</div>
|
| 146 |
""", unsafe_allow_html=True
|
| 147 |
)
|
|
@@ -213,7 +219,7 @@ def df_centered_rounded(df: pd.DataFrame, hide_index=True):
|
|
| 213 |
# =========================
|
| 214 |
def cross_plot_static(actual, pred):
|
| 215 |
a = pd.Series(actual, dtype=float)
|
| 216 |
-
p = pd.Series(pred,
|
| 217 |
|
| 218 |
fixed_min, fixed_max = 6000, 10000
|
| 219 |
ticks = np.arange(fixed_min, fixed_max + 1, 1000)
|
|
@@ -224,9 +230,6 @@ def cross_plot_static(actual, pred):
|
|
| 224 |
dpi=dpi,
|
| 225 |
constrained_layout=False
|
| 226 |
)
|
| 227 |
-
# Set the background color of the plot
|
| 228 |
-
fig.patch.set_facecolor('#0a192f')
|
| 229 |
-
ax.set_facecolor('#0a192f')
|
| 230 |
|
| 231 |
ax.scatter(a, p, s=16, c=COLORS["pred"], alpha=0.9, linewidths=0)
|
| 232 |
ax.plot([fixed_min, fixed_max], [fixed_min, fixed_max],
|
|
@@ -234,21 +237,22 @@ def cross_plot_static(actual, pred):
|
|
| 234 |
|
| 235 |
ax.set_xlim(fixed_min, fixed_max)
|
| 236 |
ax.set_ylim(fixed_min, fixed_max)
|
|
|
|
|
|
|
| 237 |
ax.set_aspect("equal", adjustable="box") # true 45°
|
| 238 |
|
| 239 |
fmt = FuncFormatter(lambda x, _: f"{int(x):,}")
|
| 240 |
ax.xaxis.set_major_formatter(fmt)
|
| 241 |
ax.yaxis.set_major_formatter(fmt)
|
| 242 |
|
| 243 |
-
|
| 244 |
-
ax.
|
| 245 |
-
ax.
|
| 246 |
-
ax.tick_params(labelsize=10, colors="#E0E7FF")
|
| 247 |
|
| 248 |
ax.grid(True, linestyle=":", alpha=0.3)
|
| 249 |
for spine in ax.spines.values():
|
| 250 |
spine.set_linewidth(1.1)
|
| 251 |
-
spine.set_color("#
|
| 252 |
|
| 253 |
fig.subplots_adjust(left=0.16, bottom=0.16, right=0.98, top=0.98)
|
| 254 |
return fig
|
|
@@ -293,40 +297,38 @@ def track_plot(df, include_actual=True):
|
|
| 293 |
|
| 294 |
fig.update_layout(
|
| 295 |
height=TRACK_H, width=None, # width auto-fits the column
|
| 296 |
-
#
|
| 297 |
-
paper_bgcolor="#0a192f", plot_bgcolor="#0a192f",
|
| 298 |
margin=dict(l=64, r=16, t=36, b=48), hovermode="closest",
|
| 299 |
-
|
| 300 |
-
font=dict(size=FONT_SZ, color="#E0E7FF"),
|
| 301 |
legend=dict(
|
| 302 |
x=0.98, y=0.05, xanchor="right", yanchor="bottom",
|
| 303 |
-
bgcolor="rgba(
|
| 304 |
),
|
| 305 |
legend_title_text=""
|
| 306 |
)
|
| 307 |
|
| 308 |
-
# Bold,
|
| 309 |
fig.update_xaxes(
|
| 310 |
title_text="UCS (psi)",
|
| 311 |
-
title_font=dict(size=16, family=BOLD_FONT, color="#
|
| 312 |
-
tickfont=dict(size=11, family=BOLD_FONT, color="#
|
| 313 |
side="top",
|
| 314 |
range=[xmin, xmax],
|
| 315 |
ticks="outside",
|
| 316 |
tickformat=",.0f",
|
| 317 |
tickmode="auto",
|
| 318 |
tick0=tick0,
|
| 319 |
-
showline=True, linewidth=1.2, linecolor="#
|
| 320 |
-
showgrid=True, gridcolor="rgba(
|
| 321 |
)
|
| 322 |
fig.update_yaxes(
|
| 323 |
title_text=ylab,
|
| 324 |
-
title_font=dict(size=16, family=BOLD_FONT, color="#
|
| 325 |
-
tickfont=dict(size=11, family=BOLD_FONT, color="#
|
| 326 |
range=y_range,
|
| 327 |
ticks="outside",
|
| 328 |
-
showline=True, linewidth=1.2, linecolor="#
|
| 329 |
-
showgrid=True, gridcolor="rgba(
|
| 330 |
)
|
| 331 |
|
| 332 |
return fig
|
|
@@ -337,22 +339,17 @@ def preview_tracks(df: pd.DataFrame, cols: list[str]):
|
|
| 337 |
n = len(cols)
|
| 338 |
if n == 0:
|
| 339 |
fig, ax = plt.subplots(figsize=(4, 2))
|
| 340 |
-
ax.text(0.5,0.5,"No selected columns",ha="center",va="center"
|
| 341 |
return fig
|
| 342 |
fig, axes = plt.subplots(1, n, figsize=(2.2*n, 7.0), sharey=True, dpi=100)
|
| 343 |
-
fig.patch.set_facecolor('#0a192f')
|
| 344 |
if n == 1: axes = [axes]
|
| 345 |
idx = np.arange(1, len(df) + 1)
|
| 346 |
for ax, col in zip(axes, cols):
|
| 347 |
-
ax.
|
| 348 |
-
ax.
|
| 349 |
-
ax.set_xlabel(col, color="#b1c7e0"); ax.xaxis.set_label_position('top'); ax.xaxis.tick_top(); ax.invert_yaxis() # Light blue labels
|
| 350 |
-
ax.tick_params(axis='both', colors='#b1c7e0') # Light blue ticks
|
| 351 |
ax.grid(True, linestyle=":", alpha=0.3)
|
| 352 |
-
for s in ax.spines.values():
|
| 353 |
-
|
| 354 |
-
s.set_edgecolor("#A0A0A0") # Lighter spine color
|
| 355 |
-
axes[0].set_ylabel("Point Index", color="#b1c7e0") # Light blue label
|
| 356 |
return fig
|
| 357 |
|
| 358 |
# Modal wrapper (Streamlit compatibility)
|
|
@@ -440,17 +437,12 @@ st.session_state.setdefault("dev_preview",False)
|
|
| 440 |
st.sidebar.markdown(f"""
|
| 441 |
<div class="centered-container">
|
| 442 |
<img src="{inline_logo('logo.png')}" style="width: 200px; height: auto; object-fit: contain;">
|
| 443 |
-
<div
|
| 444 |
-
<div style='color:#
|
| 445 |
</div>
|
| 446 |
""", unsafe_allow_html=True
|
| 447 |
)
|
| 448 |
|
| 449 |
-
# =========================
|
| 450 |
-
# Reusable Sticky Header Function
|
| 451 |
-
# This function is now removed, replaced by the fixed-header-container
|
| 452 |
-
# =========================
|
| 453 |
-
|
| 454 |
# =========================
|
| 455 |
# The main sticky container is defined here, before the app logic
|
| 456 |
# This ensures it's rendered at the top of the page regardless of state.
|
|
@@ -484,7 +476,9 @@ with st.container():
|
|
| 484 |
# =========================
|
| 485 |
if st.session_state.app_step == "intro":
|
| 486 |
st.header("Welcome!")
|
| 487 |
-
st.markdown(
|
|
|
|
|
|
|
| 488 |
st.subheader("How It Works")
|
| 489 |
st.markdown(
|
| 490 |
"1) **Upload your data to build the case and preview the performance of our model.** \n"
|
|
@@ -492,7 +486,8 @@ if st.session_state.app_step == "intro":
|
|
| 492 |
"3) **Proceed to Validation** (with actual UCS) or **Proceed to Prediction** (no UCS)."
|
| 493 |
)
|
| 494 |
if st.button("Start Showcase", type="primary"):
|
| 495 |
-
st.session_state.app_step = "dev"
|
|
|
|
| 496 |
|
| 497 |
# =========================
|
| 498 |
# CASE BUILDING
|
|
@@ -509,47 +504,70 @@ if st.session_state.app_step == "dev":
|
|
| 509 |
tmp = read_book_bytes(st.session_state.dev_file_bytes)
|
| 510 |
if tmp:
|
| 511 |
df0 = next(iter(tmp.values()))
|
| 512 |
-
st.sidebar.caption(
|
|
|
|
|
|
|
| 513 |
|
| 514 |
-
if st.sidebar.button(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 515 |
preview_modal(read_book_bytes(st.session_state.dev_file_bytes))
|
| 516 |
st.session_state.dev_preview = True
|
| 517 |
|
| 518 |
run = st.sidebar.button("Run Model", type="primary", use_container_width=True)
|
| 519 |
-
if st.sidebar.button("Proceed to Validation ▶", use_container_width=True):
|
| 520 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 521 |
|
| 522 |
if run and st.session_state.dev_file_bytes:
|
| 523 |
book = read_book_bytes(st.session_state.dev_file_bytes)
|
| 524 |
-
sh_train = find_sheet(
|
| 525 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 526 |
if sh_train is None or sh_test is None:
|
| 527 |
-
st.error(
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 531 |
tr["UCS_Pred"] = model.predict(tr[FEATURES])
|
| 532 |
te["UCS_Pred"] = model.predict(te[FEATURES])
|
| 533 |
|
| 534 |
-
st.session_state.results["Train"]=tr
|
| 535 |
-
st.session_state.results["
|
|
|
|
| 536 |
"R": pearson_r(tr[TARGET], tr["UCS_Pred"]),
|
| 537 |
"RMSE": rmse(tr[TARGET], tr["UCS_Pred"]),
|
| 538 |
"MAE": mean_absolute_error(tr[TARGET], tr["UCS_Pred"])
|
| 539 |
}
|
| 540 |
-
st.session_state.results["m_test"]={
|
| 541 |
"R": pearson_r(te[TARGET], te["UCS_Pred"]),
|
| 542 |
"RMSE": rmse(te[TARGET], te["UCS_Pred"]),
|
| 543 |
"MAE": mean_absolute_error(te[TARGET], te["UCS_Pred"])
|
| 544 |
}
|
| 545 |
|
| 546 |
-
tr_min = tr[FEATURES].min().to_dict()
|
| 547 |
-
|
|
|
|
| 548 |
st.success("Case has been built and results are displayed below.")
|
| 549 |
|
| 550 |
def _dev_block(df, m):
|
| 551 |
-
c1,c2,c3 = st.columns(3)
|
| 552 |
-
c1.metric("R", f"{m['R']:.2f}")
|
|
|
|
|
|
|
| 553 |
|
| 554 |
# 2-column layout, big gap (prevents overlap)
|
| 555 |
col_cross, col_track = st.columns([3, 2], gap="large")
|
|
@@ -565,16 +583,18 @@ if st.session_state.app_step == "dev":
|
|
| 565 |
if "Train" in st.session_state.results or "Test" in st.session_state.results:
|
| 566 |
tab1, tab2 = st.tabs(["Training", "Testing"])
|
| 567 |
if "Train" in st.session_state.results:
|
| 568 |
-
with tab1:
|
|
|
|
| 569 |
if "Test" in st.session_state.results:
|
| 570 |
-
with tab2:
|
|
|
|
| 571 |
|
| 572 |
# =========================
|
| 573 |
# VALIDATION (with actual UCS)
|
| 574 |
# =========================
|
| 575 |
if st.session_state.app_step == "validate":
|
| 576 |
st.sidebar.header("Validate the Model")
|
| 577 |
-
up = st.sidebar.file_uploader("Upload Validation Excel", type=["xlsx","xls"])
|
| 578 |
if up is not None:
|
| 579 |
book = read_book_bytes(up.getvalue())
|
| 580 |
if book:
|
|
@@ -583,54 +603,78 @@ if st.session_state.app_step == "validate":
|
|
| 583 |
if st.sidebar.button("Preview data", use_container_width=True, disabled=(up is None)):
|
| 584 |
preview_modal(read_book_bytes(up.getvalue()))
|
| 585 |
go_btn = st.sidebar.button("Predict", type="primary", use_container_width=True)
|
| 586 |
-
if st.sidebar.button("⬅ Back to Case Building", use_container_width=True):
|
| 587 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 588 |
|
| 589 |
if go_btn and up is not None:
|
| 590 |
book = read_book_bytes(up.getvalue())
|
| 591 |
-
name = find_sheet(book, ["Validation","Validate","validation2","Val","val"]) or list(book.keys())[0]
|
| 592 |
df = book[name].copy()
|
| 593 |
-
if not ensure_cols(df, FEATURES+[TARGET]):
|
|
|
|
|
|
|
| 594 |
df["UCS_Pred"] = model.predict(df[FEATURES])
|
| 595 |
-
st.session_state.results["Validate"]=df
|
| 596 |
|
| 597 |
-
ranges = st.session_state.train_ranges
|
|
|
|
|
|
|
| 598 |
if ranges:
|
| 599 |
-
any_viol = pd.DataFrame(
|
| 600 |
-
|
|
|
|
|
|
|
| 601 |
if any_viol.any():
|
| 602 |
tbl = df.loc[any_viol, FEATURES].copy()
|
| 603 |
for c in FEATURES:
|
| 604 |
-
if pd.api.types.is_numeric_dtype(tbl[c]):
|
| 605 |
-
|
| 606 |
-
|
|
|
|
|
|
|
|
|
|
| 607 |
"R": pearson_r(df[TARGET], df["UCS_Pred"]),
|
| 608 |
"RMSE": rmse(df[TARGET], df["UCS_Pred"]),
|
| 609 |
"MAE": mean_absolute_error(df[TARGET], df["UCS_Pred"])
|
| 610 |
}
|
| 611 |
-
st.session_state.results["sv_val"]={
|
| 612 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 613 |
|
| 614 |
if "Validate" in st.session_state.results:
|
| 615 |
m = st.session_state.results["m_val"]
|
| 616 |
-
c1,c2,c3 = st.columns(3)
|
| 617 |
-
c1.metric("R", f"{m['R']:.2f}")
|
|
|
|
|
|
|
| 618 |
|
| 619 |
col_cross, col_track = st.columns([3, 2], gap="large")
|
| 620 |
with col_cross:
|
| 621 |
st.pyplot(
|
| 622 |
-
cross_plot_static(
|
| 623 |
-
|
|
|
|
|
|
|
| 624 |
use_container_width=True
|
| 625 |
)
|
| 626 |
with col_track:
|
| 627 |
st.plotly_chart(
|
| 628 |
track_plot(st.session_state.results["Validate"], include_actual=True),
|
| 629 |
-
use_container_width=True,
|
|
|
|
| 630 |
)
|
| 631 |
|
| 632 |
sv = st.session_state.results["sv_val"]
|
| 633 |
-
if sv["oor"] > 0:
|
|
|
|
| 634 |
if st.session_state.results["oor_tbl"] is not None:
|
| 635 |
st.write("*Out-of-range rows (vs. Training min–max):*")
|
| 636 |
df_centered_rounded(st.session_state.results["oor_tbl"])
|
|
@@ -640,7 +684,7 @@ if st.session_state.app_step == "validate":
|
|
| 640 |
# =========================
|
| 641 |
if st.session_state.app_step == "predict":
|
| 642 |
st.sidebar.header("Prediction (No Actual UCS)")
|
| 643 |
-
up = st.sidebar.file_uploader("Upload Prediction Excel", type=["xlsx","xls"])
|
| 644 |
if up is not None:
|
| 645 |
book = read_book_bytes(up.getvalue())
|
| 646 |
if book:
|
|
@@ -649,41 +693,52 @@ if st.session_state.app_step == "predict":
|
|
| 649 |
if st.sidebar.button("Preview data", use_container_width=True, disabled=(up is None)):
|
| 650 |
preview_modal(read_book_bytes(up.getvalue()))
|
| 651 |
go_btn = st.sidebar.button("Predict", type="primary", use_container_width=True)
|
| 652 |
-
if st.sidebar.button("⬅ Back to Case Building", use_container_width=True):
|
|
|
|
|
|
|
| 653 |
|
| 654 |
if go_btn and up is not None:
|
| 655 |
-
book = read_book_bytes(up.getvalue())
|
|
|
|
| 656 |
df = book[name].copy()
|
| 657 |
-
if not ensure_cols(df, FEATURES):
|
|
|
|
|
|
|
| 658 |
df["UCS_Pred"] = model.predict(df[FEATURES])
|
| 659 |
-
st.session_state.results["PredictOnly"]=df
|
| 660 |
|
| 661 |
-
ranges = st.session_state.train_ranges
|
|
|
|
| 662 |
if ranges:
|
| 663 |
-
any_viol = pd.DataFrame(
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
"
|
| 669 |
-
"
|
| 670 |
-
"
|
| 671 |
-
"
|
|
|
|
|
|
|
| 672 |
}
|
| 673 |
|
| 674 |
if "PredictOnly" in st.session_state.results:
|
| 675 |
-
df = st.session_state.results["PredictOnly"]
|
|
|
|
| 676 |
|
| 677 |
-
col_left, col_right = st.columns([2,3], gap="large")
|
| 678 |
with col_left:
|
| 679 |
table = pd.DataFrame({
|
| 680 |
-
"Metric": ["# points","Pred min","Pred max","Pred mean","Pred std","OOR %"],
|
| 681 |
-
"Value":
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
|
|
|
|
|
|
| 687 |
})
|
| 688 |
st.success("Predictions ready ✓")
|
| 689 |
df_centered_rounded(table, hide_index=True)
|
|
@@ -691,7 +746,8 @@ if st.session_state.app_step == "predict":
|
|
| 691 |
with col_right:
|
| 692 |
st.plotly_chart(
|
| 693 |
track_plot(df, include_actual=False),
|
| 694 |
-
use_container_width=True,
|
|
|
|
| 695 |
)
|
| 696 |
|
| 697 |
# =========================
|
|
@@ -700,10 +756,10 @@ if st.session_state.app_step == "predict":
|
|
| 700 |
st.markdown("---")
|
| 701 |
st.markdown(
|
| 702 |
"""
|
| 703 |
-
<div style='text-align:center; color:#
|
| 704 |
ST_GeoMech_UCS • © Smart Thinking<br/>
|
| 705 |
<strong>Visit our website:</strong> <a href='https://www.smartthinking.com.sa' target='_blank'>smartthinking.com.sa</a>
|
| 706 |
</div>
|
| 707 |
""",
|
| 708 |
unsafe_allow_html=True
|
| 709 |
-
)
|
|
|
|
| 22 |
MODELS_DIR = Path("models")
|
| 23 |
DEFAULT_MODEL = MODELS_DIR / "ucs_rf.joblib"
|
| 24 |
MODEL_FALLBACKS = [MODELS_DIR / "model.joblib", MODELS_DIR / "model.pkl"]
|
| 25 |
+
COLORS = {"pred": "#1f77b4", "actual": "#f2b702", "ref": "#5a5a5a"}
|
|
|
|
|
|
|
| 26 |
|
| 27 |
# ---- Plot sizing controls ----
|
| 28 |
CROSS_W = 450 # px (matplotlib figure size; Streamlit will still scale)
|
|
|
|
| 36 |
# =========================
|
| 37 |
st.set_page_config(page_title="ST_GeoMech_UCS", page_icon="logo.png", layout="wide")
|
| 38 |
|
| 39 |
+
# This is the new CSS block with the added h1 color rule
|
| 40 |
st.markdown("""
|
| 41 |
<style>
|
| 42 |
+
/* Add a light gray background to the main app container */
|
| 43 |
+
.stApp {
|
| 44 |
+
background-color: #f0f2f6;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
/* Change the color of the main page title (h1) to light blue */
|
| 48 |
+
h1 {
|
| 49 |
+
color: #add8e6;
|
| 50 |
}
|
| 51 |
|
| 52 |
+
/* CSS to make the header sticky */
|
| 53 |
.fixed-header-container {
|
| 54 |
position: sticky;
|
| 55 |
top: 0;
|
| 56 |
z-index: 1000;
|
| 57 |
+
background-color: #f0f2f6; /* Ensure sticky header background matches app background */
|
| 58 |
padding: 10px 0;
|
| 59 |
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
}
|
| 61 |
|
| 62 |
+
/* CSS to ensure main content doesn't get hidden under the sticky header */
|
| 63 |
.main .block-container {
|
| 64 |
+
padding-top: 250px; /* Adjust this value to match the height of your sticky header */
|
| 65 |
}
|
| 66 |
|
| 67 |
/* General CSS */
|
| 68 |
.brand-logo { width: 16px; height: auto; object-fit: contain; }
|
| 69 |
.sidebar-header { display:flex; align-items:center; gap:12px; }
|
| 70 |
.sidebar-header .text h1 { font-size: 1.05rem; margin:0; line-height:1.1; }
|
| 71 |
+
.sidebar-header .text .tag { font-size: .85rem; color:#6b7280; margin:2px 0 0; }
|
| 72 |
.centered-container {
|
| 73 |
display: flex;
|
| 74 |
flex-direction: column;
|
|
|
|
| 78 |
</style>
|
| 79 |
""", unsafe_allow_html=True)
|
| 80 |
|
| 81 |
+
|
| 82 |
+
# Hide uploader helper text ("Drag and drop file here", limits, etc.)
|
| 83 |
+
st.markdown("""
|
| 84 |
+
<style>
|
| 85 |
+
/* Older builds (helper wrapped in a Markdown container) */
|
| 86 |
+
section[data-testid="stFileUploader"] div[data-testid="stMarkdownContainer"]{display:none !important;}
|
| 87 |
+
/* 1.31–1.34: helper is the first child in the dropzone */
|
| 88 |
+
section[data-testid="stFileUploader"] [data-testid="stFileUploaderDropzone"] > div:first-child{display:none !important;}
|
| 89 |
+
/* 1.35+: explicit helper container */
|
| 90 |
+
section[data-testid="stFileUploader"] [data-testid="stFileUploaderInstructions"]{display:none !important;}
|
| 91 |
+
/* Fallback: any paragraph/small text inside the uploader */
|
| 92 |
+
section[data-testid="stFileUploader"] p, section[data-testid="stFileUploader"] small{display:none !important;}
|
| 93 |
+
</style>
|
| 94 |
+
""", unsafe_allow_html=True)
|
| 95 |
+
|
| 96 |
# Make the Preview expander title & tabs sticky (pinned to the top)
|
| 97 |
st.markdown("""
|
| 98 |
<style>
|
|
|
|
| 99 |
div[data-testid="stExpander"] > details > summary {
|
| 100 |
+
position: sticky;
|
| 101 |
+
top: 0;
|
| 102 |
+
z-index: 10;
|
| 103 |
+
background: #fff;
|
| 104 |
+
border-bottom: 1px solid #eee;
|
| 105 |
}
|
|
|
|
| 106 |
div[data-testid="stExpander"] div[data-baseweb="tab-list"] {
|
| 107 |
+
position: sticky;
|
| 108 |
+
top: 42px; /* adjust if your expander header height differs */
|
| 109 |
+
z-index: 9;
|
| 110 |
+
background: #fff;
|
| 111 |
+
padding-top: 6px;
|
| 112 |
}
|
| 113 |
</style>
|
| 114 |
""", unsafe_allow_html=True)
|
|
|
|
| 146 |
st.sidebar.markdown(f"""
|
| 147 |
<div class="centered-container">
|
| 148 |
<img src="{inline_logo('logo.png')}" style="width: 200px; height: auto; object-fit: contain;">
|
| 149 |
+
<div style='font-weight:800;font-size:1.2rem; margin-top: 10px;'>ST_GeoMech_UCS</div>
|
| 150 |
+
<div style='color:#667085;'>Smart Thinking • Secure Access</div>
|
| 151 |
</div>
|
| 152 |
""", unsafe_allow_html=True
|
| 153 |
)
|
|
|
|
| 219 |
# =========================
|
| 220 |
def cross_plot_static(actual, pred):
|
| 221 |
a = pd.Series(actual, dtype=float)
|
| 222 |
+
p = pd.Series(pred, dtype=float)
|
| 223 |
|
| 224 |
fixed_min, fixed_max = 6000, 10000
|
| 225 |
ticks = np.arange(fixed_min, fixed_max + 1, 1000)
|
|
|
|
| 230 |
dpi=dpi,
|
| 231 |
constrained_layout=False
|
| 232 |
)
|
|
|
|
|
|
|
|
|
|
| 233 |
|
| 234 |
ax.scatter(a, p, s=16, c=COLORS["pred"], alpha=0.9, linewidths=0)
|
| 235 |
ax.plot([fixed_min, fixed_max], [fixed_min, fixed_max],
|
|
|
|
| 237 |
|
| 238 |
ax.set_xlim(fixed_min, fixed_max)
|
| 239 |
ax.set_ylim(fixed_min, fixed_max)
|
| 240 |
+
ax.set_xticks(ticks)
|
| 241 |
+
ax.set_yticks(ticks)
|
| 242 |
ax.set_aspect("equal", adjustable="box") # true 45°
|
| 243 |
|
| 244 |
fmt = FuncFormatter(lambda x, _: f"{int(x):,}")
|
| 245 |
ax.xaxis.set_major_formatter(fmt)
|
| 246 |
ax.yaxis.set_major_formatter(fmt)
|
| 247 |
|
| 248 |
+
ax.set_xlabel("Actual UCS (psi)", fontweight="bold", fontsize=12, color="black")
|
| 249 |
+
ax.set_ylabel("Predicted UCS (psi)", fontweight="bold", fontsize=12, color="black")
|
| 250 |
+
ax.tick_params(labelsize=10, colors="black")
|
|
|
|
| 251 |
|
| 252 |
ax.grid(True, linestyle=":", alpha=0.3)
|
| 253 |
for spine in ax.spines.values():
|
| 254 |
spine.set_linewidth(1.1)
|
| 255 |
+
spine.set_color("#444")
|
| 256 |
|
| 257 |
fig.subplots_adjust(left=0.16, bottom=0.16, right=0.98, top=0.98)
|
| 258 |
return fig
|
|
|
|
| 297 |
|
| 298 |
fig.update_layout(
|
| 299 |
height=TRACK_H, width=None, # width auto-fits the column
|
| 300 |
+
paper_bgcolor="#fff", plot_bgcolor="#fff",
|
|
|
|
| 301 |
margin=dict(l=64, r=16, t=36, b=48), hovermode="closest",
|
| 302 |
+
font=dict(size=FONT_SZ, color="#000"),
|
|
|
|
| 303 |
legend=dict(
|
| 304 |
x=0.98, y=0.05, xanchor="right", yanchor="bottom",
|
| 305 |
+
bgcolor="rgba(255,255,255,0.75)", bordercolor="#ccc", borderwidth=1
|
| 306 |
),
|
| 307 |
legend_title_text=""
|
| 308 |
)
|
| 309 |
|
| 310 |
+
# Bold, black axis titles & ticks
|
| 311 |
fig.update_xaxes(
|
| 312 |
title_text="UCS (psi)",
|
| 313 |
+
title_font=dict(size=16, family=BOLD_FONT, color="#000"),
|
| 314 |
+
tickfont=dict(size=11, family=BOLD_FONT, color="#000"),
|
| 315 |
side="top",
|
| 316 |
range=[xmin, xmax],
|
| 317 |
ticks="outside",
|
| 318 |
tickformat=",.0f",
|
| 319 |
tickmode="auto",
|
| 320 |
tick0=tick0,
|
| 321 |
+
showline=True, linewidth=1.2, linecolor="#444", mirror=True,
|
| 322 |
+
showgrid=True, gridcolor="rgba(0,0,0,0.12)", automargin=True
|
| 323 |
)
|
| 324 |
fig.update_yaxes(
|
| 325 |
title_text=ylab,
|
| 326 |
+
title_font=dict(size=16, family=BOLD_FONT, color="#000"),
|
| 327 |
+
tickfont=dict(size=11, family=BOLD_FONT, color="#000"),
|
| 328 |
range=y_range,
|
| 329 |
ticks="outside",
|
| 330 |
+
showline=True, linewidth=1.2, linecolor="#444", mirror=True,
|
| 331 |
+
showgrid=True, gridcolor="rgba(0,0,0,0.12)", automargin=True
|
| 332 |
)
|
| 333 |
|
| 334 |
return fig
|
|
|
|
| 339 |
n = len(cols)
|
| 340 |
if n == 0:
|
| 341 |
fig, ax = plt.subplots(figsize=(4, 2))
|
| 342 |
+
ax.text(0.5,0.5,"No selected columns",ha="center",va="center"); ax.axis("off")
|
| 343 |
return fig
|
| 344 |
fig, axes = plt.subplots(1, n, figsize=(2.2*n, 7.0), sharey=True, dpi=100)
|
|
|
|
| 345 |
if n == 1: axes = [axes]
|
| 346 |
idx = np.arange(1, len(df) + 1)
|
| 347 |
for ax, col in zip(axes, cols):
|
| 348 |
+
ax.plot(df[col], idx, '-', lw=1.4, color="#333")
|
| 349 |
+
ax.set_xlabel(col); ax.xaxis.set_label_position('top'); ax.xaxis.tick_top(); ax.invert_yaxis()
|
|
|
|
|
|
|
| 350 |
ax.grid(True, linestyle=":", alpha=0.3)
|
| 351 |
+
for s in ax.spines.values(): s.set_visible(True)
|
| 352 |
+
axes[0].set_ylabel("Point Index")
|
|
|
|
|
|
|
| 353 |
return fig
|
| 354 |
|
| 355 |
# Modal wrapper (Streamlit compatibility)
|
|
|
|
| 437 |
st.sidebar.markdown(f"""
|
| 438 |
<div class="centered-container">
|
| 439 |
<img src="{inline_logo('logo.png')}" style="width: 200px; height: auto; object-fit: contain;">
|
| 440 |
+
<div style='font-weight:800;font-size:1.2rem;'>ST_GeoMech_UCS</div>
|
| 441 |
+
<div style='color:#667085;'>Real-Time UCS Tracking While Drilling</div>
|
| 442 |
</div>
|
| 443 |
""", unsafe_allow_html=True
|
| 444 |
)
|
| 445 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 446 |
# =========================
|
| 447 |
# The main sticky container is defined here, before the app logic
|
| 448 |
# This ensures it's rendered at the top of the page regardless of state.
|
|
|
|
| 476 |
# =========================
|
| 477 |
if st.session_state.app_step == "intro":
|
| 478 |
st.header("Welcome!")
|
| 479 |
+
st.markdown(
|
| 480 |
+
"This software is developed by *Smart Thinking AI-Solutions Team* to estimate UCS from drilling data."
|
| 481 |
+
)
|
| 482 |
st.subheader("How It Works")
|
| 483 |
st.markdown(
|
| 484 |
"1) **Upload your data to build the case and preview the performance of our model.** \n"
|
|
|
|
| 486 |
"3) **Proceed to Validation** (with actual UCS) or **Proceed to Prediction** (no UCS)."
|
| 487 |
)
|
| 488 |
if st.button("Start Showcase", type="primary"):
|
| 489 |
+
st.session_state.app_step = "dev"
|
| 490 |
+
st.rerun()
|
| 491 |
|
| 492 |
# =========================
|
| 493 |
# CASE BUILDING
|
|
|
|
| 504 |
tmp = read_book_bytes(st.session_state.dev_file_bytes)
|
| 505 |
if tmp:
|
| 506 |
df0 = next(iter(tmp.values()))
|
| 507 |
+
st.sidebar.caption(
|
| 508 |
+
f"**Data loaded:** {st.session_state.dev_file_name} • {df0.shape[0]} rows × {df0.shape[1]} cols"
|
| 509 |
+
)
|
| 510 |
|
| 511 |
+
if st.sidebar.button(
|
| 512 |
+
"Preview data",
|
| 513 |
+
use_container_width=True,
|
| 514 |
+
disabled=not st.session_state.dev_file_loaded
|
| 515 |
+
):
|
| 516 |
preview_modal(read_book_bytes(st.session_state.dev_file_bytes))
|
| 517 |
st.session_state.dev_preview = True
|
| 518 |
|
| 519 |
run = st.sidebar.button("Run Model", type="primary", use_container_width=True)
|
| 520 |
+
if st.sidebar.button("Proceed to Validation ▶", use_container_width=True):
|
| 521 |
+
st.session_state.app_step = "validate"
|
| 522 |
+
st.rerun()
|
| 523 |
+
if st.sidebar.button("Proceed to Prediction ▶", use_container_width=True):
|
| 524 |
+
st.session_state.app_step = "predict"
|
| 525 |
+
st.rerun()
|
| 526 |
|
| 527 |
if run and st.session_state.dev_file_bytes:
|
| 528 |
book = read_book_bytes(st.session_state.dev_file_bytes)
|
| 529 |
+
sh_train = find_sheet(
|
| 530 |
+
book, ["Train", "Training", "training2", "train", "training"]
|
| 531 |
+
)
|
| 532 |
+
sh_test = find_sheet(
|
| 533 |
+
book, ["Test", "Testing", "testing2", "test", "testing"]
|
| 534 |
+
)
|
| 535 |
if sh_train is None or sh_test is None:
|
| 536 |
+
st.error(
|
| 537 |
+
"Workbook must include Train/Training/training2 and Test/Testing/testing2 sheets."
|
| 538 |
+
)
|
| 539 |
+
st.stop()
|
| 540 |
+
tr = book[sh_train].copy()
|
| 541 |
+
te = book[sh_test].copy()
|
| 542 |
+
if not (ensure_cols(tr, FEATURES + [TARGET]) and ensure_cols(te, FEATURES + [TARGET])):
|
| 543 |
+
st.error("Missing required columns.")
|
| 544 |
+
st.stop()
|
| 545 |
tr["UCS_Pred"] = model.predict(tr[FEATURES])
|
| 546 |
te["UCS_Pred"] = model.predict(te[FEATURES])
|
| 547 |
|
| 548 |
+
st.session_state.results["Train"] = tr
|
| 549 |
+
st.session_state.results["Test"] = te
|
| 550 |
+
st.session_state.results["m_train"] = {
|
| 551 |
"R": pearson_r(tr[TARGET], tr["UCS_Pred"]),
|
| 552 |
"RMSE": rmse(tr[TARGET], tr["UCS_Pred"]),
|
| 553 |
"MAE": mean_absolute_error(tr[TARGET], tr["UCS_Pred"])
|
| 554 |
}
|
| 555 |
+
st.session_state.results["m_test"] = {
|
| 556 |
"R": pearson_r(te[TARGET], te["UCS_Pred"]),
|
| 557 |
"RMSE": rmse(te[TARGET], te["UCS_Pred"]),
|
| 558 |
"MAE": mean_absolute_error(te[TARGET], te["UCS_Pred"])
|
| 559 |
}
|
| 560 |
|
| 561 |
+
tr_min = tr[FEATURES].min().to_dict()
|
| 562 |
+
tr_max = tr[FEATURES].max().to_dict()
|
| 563 |
+
st.session_state.train_ranges = {f: (float(tr_min[f]), float(tr_max[f])) for f in FEATURES}
|
| 564 |
st.success("Case has been built and results are displayed below.")
|
| 565 |
|
| 566 |
def _dev_block(df, m):
|
| 567 |
+
c1, c2, c3 = st.columns(3)
|
| 568 |
+
c1.metric("R", f"{m['R']:.2f}")
|
| 569 |
+
c2.metric("RMSE", f"{m['RMSE']:.2f}")
|
| 570 |
+
c3.metric("MAE", f"{m['MAE']:.2f}")
|
| 571 |
|
| 572 |
# 2-column layout, big gap (prevents overlap)
|
| 573 |
col_cross, col_track = st.columns([3, 2], gap="large")
|
|
|
|
| 583 |
if "Train" in st.session_state.results or "Test" in st.session_state.results:
|
| 584 |
tab1, tab2 = st.tabs(["Training", "Testing"])
|
| 585 |
if "Train" in st.session_state.results:
|
| 586 |
+
with tab1:
|
| 587 |
+
_dev_block(st.session_state.results["Train"], st.session_state.results["m_train"])
|
| 588 |
if "Test" in st.session_state.results:
|
| 589 |
+
with tab2:
|
| 590 |
+
_dev_block(st.session_state.results["Test"], st.session_state.results["m_test"])
|
| 591 |
|
| 592 |
# =========================
|
| 593 |
# VALIDATION (with actual UCS)
|
| 594 |
# =========================
|
| 595 |
if st.session_state.app_step == "validate":
|
| 596 |
st.sidebar.header("Validate the Model")
|
| 597 |
+
up = st.sidebar.file_uploader("Upload Validation Excel", type=["xlsx", "xls"])
|
| 598 |
if up is not None:
|
| 599 |
book = read_book_bytes(up.getvalue())
|
| 600 |
if book:
|
|
|
|
| 603 |
if st.sidebar.button("Preview data", use_container_width=True, disabled=(up is None)):
|
| 604 |
preview_modal(read_book_bytes(up.getvalue()))
|
| 605 |
go_btn = st.sidebar.button("Predict", type="primary", use_container_width=True)
|
| 606 |
+
if st.sidebar.button("⬅ Back to Case Building", use_container_width=True):
|
| 607 |
+
st.session_state.app_step = "dev"
|
| 608 |
+
st.rerun()
|
| 609 |
+
if st.sidebar.button("Proceed to Prediction ▶", use_container_width=True):
|
| 610 |
+
st.session_state.app_step = "predict"
|
| 611 |
+
st.rerun()
|
| 612 |
|
| 613 |
if go_btn and up is not None:
|
| 614 |
book = read_book_bytes(up.getvalue())
|
| 615 |
+
name = find_sheet(book, ["Validation", "Validate", "validation2", "Val", "val"]) or list(book.keys())[0]
|
| 616 |
df = book[name].copy()
|
| 617 |
+
if not ensure_cols(df, FEATURES + [TARGET]):
|
| 618 |
+
st.error("Missing required columns.")
|
| 619 |
+
st.stop()
|
| 620 |
df["UCS_Pred"] = model.predict(df[FEATURES])
|
| 621 |
+
st.session_state.results["Validate"] = df
|
| 622 |
|
| 623 |
+
ranges = st.session_state.train_ranges
|
| 624 |
+
oor_pct = 0.0
|
| 625 |
+
tbl = None
|
| 626 |
if ranges:
|
| 627 |
+
any_viol = pd.DataFrame(
|
| 628 |
+
{f: (df[f] < ranges[f][0]) | (df[f] > ranges[f][1]) for f in FEATURES}
|
| 629 |
+
).any(axis=1)
|
| 630 |
+
oor_pct = float(any_viol.mean() * 100.0)
|
| 631 |
if any_viol.any():
|
| 632 |
tbl = df.loc[any_viol, FEATURES].copy()
|
| 633 |
for c in FEATURES:
|
| 634 |
+
if pd.api.types.is_numeric_dtype(tbl[c]):
|
| 635 |
+
tbl[c] = tbl[c].round(2)
|
| 636 |
+
tbl["Violations"] = pd.DataFrame(
|
| 637 |
+
{f: (df[f] < ranges[f][0]) | (df[f] > ranges[f][1]) for f in FEATURES}
|
| 638 |
+
).loc[any_viol].apply(lambda r: ", ".join([c for c, v in r.items() if v]), axis=1)
|
| 639 |
+
st.session_state.results["m_val"] = {
|
| 640 |
"R": pearson_r(df[TARGET], df["UCS_Pred"]),
|
| 641 |
"RMSE": rmse(df[TARGET], df["UCS_Pred"]),
|
| 642 |
"MAE": mean_absolute_error(df[TARGET], df["UCS_Pred"])
|
| 643 |
}
|
| 644 |
+
st.session_state.results["sv_val"] = {
|
| 645 |
+
"n": len(df),
|
| 646 |
+
"pred_min": float(df["UCS_Pred"].min()),
|
| 647 |
+
"pred_max": float(df["UCS_Pred"].max()),
|
| 648 |
+
"oor": oor_pct
|
| 649 |
+
}
|
| 650 |
+
st.session_state.results["oor_tbl"] = tbl
|
| 651 |
|
| 652 |
if "Validate" in st.session_state.results:
|
| 653 |
m = st.session_state.results["m_val"]
|
| 654 |
+
c1, c2, c3 = st.columns(3)
|
| 655 |
+
c1.metric("R", f"{m['R']:.2f}")
|
| 656 |
+
c2.metric("RMSE", f"{m['RMSE']:.2f}")
|
| 657 |
+
c3.metric("MAE", f"{m['MAE']:.2f}")
|
| 658 |
|
| 659 |
col_cross, col_track = st.columns([3, 2], gap="large")
|
| 660 |
with col_cross:
|
| 661 |
st.pyplot(
|
| 662 |
+
cross_plot_static(
|
| 663 |
+
st.session_state.results["Validate"][TARGET],
|
| 664 |
+
st.session_state.results["Validate"]["UCS_Pred"]
|
| 665 |
+
),
|
| 666 |
use_container_width=True
|
| 667 |
)
|
| 668 |
with col_track:
|
| 669 |
st.plotly_chart(
|
| 670 |
track_plot(st.session_state.results["Validate"], include_actual=True),
|
| 671 |
+
use_container_width=True,
|
| 672 |
+
config={"displayModeBar": False, "scrollZoom": True}
|
| 673 |
)
|
| 674 |
|
| 675 |
sv = st.session_state.results["sv_val"]
|
| 676 |
+
if sv["oor"] > 0:
|
| 677 |
+
st.warning("Some inputs fall outside **training min–max** ranges.")
|
| 678 |
if st.session_state.results["oor_tbl"] is not None:
|
| 679 |
st.write("*Out-of-range rows (vs. Training min–max):*")
|
| 680 |
df_centered_rounded(st.session_state.results["oor_tbl"])
|
|
|
|
| 684 |
# =========================
|
| 685 |
if st.session_state.app_step == "predict":
|
| 686 |
st.sidebar.header("Prediction (No Actual UCS)")
|
| 687 |
+
up = st.sidebar.file_uploader("Upload Prediction Excel", type=["xlsx", "xls"])
|
| 688 |
if up is not None:
|
| 689 |
book = read_book_bytes(up.getvalue())
|
| 690 |
if book:
|
|
|
|
| 693 |
if st.sidebar.button("Preview data", use_container_width=True, disabled=(up is None)):
|
| 694 |
preview_modal(read_book_bytes(up.getvalue()))
|
| 695 |
go_btn = st.sidebar.button("Predict", type="primary", use_container_width=True)
|
| 696 |
+
if st.sidebar.button("⬅ Back to Case Building", use_container_width=True):
|
| 697 |
+
st.session_state.app_step = "dev"
|
| 698 |
+
st.rerun()
|
| 699 |
|
| 700 |
if go_btn and up is not None:
|
| 701 |
+
book = read_book_bytes(up.getvalue())
|
| 702 |
+
name = list(book.keys())[0]
|
| 703 |
df = book[name].copy()
|
| 704 |
+
if not ensure_cols(df, FEATURES):
|
| 705 |
+
st.error("Missing required columns.")
|
| 706 |
+
st.stop()
|
| 707 |
df["UCS_Pred"] = model.predict(df[FEATURES])
|
| 708 |
+
st.session_state.results["PredictOnly"] = df
|
| 709 |
|
| 710 |
+
ranges = st.session_state.train_ranges
|
| 711 |
+
oor_pct = 0.0
|
| 712 |
if ranges:
|
| 713 |
+
any_viol = pd.DataFrame(
|
| 714 |
+
{f: (df[f] < ranges[f][0]) | (df[f] > ranges[f][1]) for f in FEATURES}
|
| 715 |
+
).any(axis=1)
|
| 716 |
+
oor_pct = float(any_viol.mean() * 100.0)
|
| 717 |
+
st.session_state.results["sv_pred"] = {
|
| 718 |
+
"n": len(df),
|
| 719 |
+
"pred_min": float(df["UCS_Pred"].min()),
|
| 720 |
+
"pred_max": float(df["UCS_Pred"].max()),
|
| 721 |
+
"pred_mean": float(df["UCS_Pred"].mean()),
|
| 722 |
+
"pred_std": float(df["UCS_Pred"].std(ddof=0)),
|
| 723 |
+
"oor": oor_pct
|
| 724 |
}
|
| 725 |
|
| 726 |
if "PredictOnly" in st.session_state.results:
|
| 727 |
+
df = st.session_state.results["PredictOnly"]
|
| 728 |
+
sv = st.session_state.results["sv_pred"]
|
| 729 |
|
| 730 |
+
col_left, col_right = st.columns([2, 3], gap="large")
|
| 731 |
with col_left:
|
| 732 |
table = pd.DataFrame({
|
| 733 |
+
"Metric": ["# points", "Pred min", "Pred max", "Pred mean", "Pred std", "OOR %"],
|
| 734 |
+
"Value": [
|
| 735 |
+
sv["n"],
|
| 736 |
+
round(sv["pred_min"], 2),
|
| 737 |
+
round(sv["pred_max"], 2),
|
| 738 |
+
round(sv["pred_mean"], 2),
|
| 739 |
+
round(sv["pred_std"], 2),
|
| 740 |
+
f'{sv["oor"]:.1f}%'
|
| 741 |
+
]
|
| 742 |
})
|
| 743 |
st.success("Predictions ready ✓")
|
| 744 |
df_centered_rounded(table, hide_index=True)
|
|
|
|
| 746 |
with col_right:
|
| 747 |
st.plotly_chart(
|
| 748 |
track_plot(df, include_actual=False),
|
| 749 |
+
use_container_width=True,
|
| 750 |
+
config={"displayModeBar": False, "scrollZoom": True}
|
| 751 |
)
|
| 752 |
|
| 753 |
# =========================
|
|
|
|
| 756 |
st.markdown("---")
|
| 757 |
st.markdown(
|
| 758 |
"""
|
| 759 |
+
<div style='text-align:center; color:#6b7280; line-height:1.6'>
|
| 760 |
ST_GeoMech_UCS • © Smart Thinking<br/>
|
| 761 |
<strong>Visit our website:</strong> <a href='https://www.smartthinking.com.sa' target='_blank'>smartthinking.com.sa</a>
|
| 762 |
</div>
|
| 763 |
""",
|
| 764 |
unsafe_allow_html=True
|
| 765 |
+
)
|