Update app.py
Browse files
app.py
CHANGED
|
@@ -29,15 +29,15 @@ from sklearn.impute import SimpleImputer
|
|
| 29 |
APP_NAME = "ST_Min_Horizontal_Stress"
|
| 30 |
TAGLINE = "Real-Time Minimum Horizontal Stress Prediction"
|
| 31 |
|
| 32 |
-
# --------
|
| 33 |
FEATURES = ["Q (gpm)", "SPP (psi)", "T (kft.lbf)", "WOB (klbf)", "ROP (ft/h)"]
|
| 34 |
TARGET = "MINStress_Actual"
|
| 35 |
PRED_COL = "MINStress_Pred"
|
| 36 |
ACTUAL_COL = TARGET
|
| 37 |
TRANSFORM = "none" # "none" | "log10" | "ln"
|
| 38 |
-
UNITS = "
|
| 39 |
|
| 40 |
-
# ---- "
|
| 41 |
BEST_PARAMS = dict(
|
| 42 |
n_estimators=400,
|
| 43 |
max_depth=None,
|
|
@@ -354,7 +354,7 @@ def cross_plot_static(actual, pred):
|
|
| 354 |
ax.set_xticks(ticks); ax.set_yticks(ticks)
|
| 355 |
ax.set_aspect("equal", adjustable="box")
|
| 356 |
|
| 357 |
-
fmt = FuncFormatter(lambda x, _: f"{x:.
|
| 358 |
ax.xaxis.set_major_formatter(fmt); ax.yaxis.set_major_formatter(fmt)
|
| 359 |
|
| 360 |
ax.set_xlabel(f"Actual Min Stress ({UNITS})", fontweight="bold", fontsize=10, color="black")
|
|
@@ -415,7 +415,9 @@ def track_plot(df, include_actual=True):
|
|
| 415 |
title_font=dict(size=20, family=BOLD_FONT, color="#000"),
|
| 416 |
tickfont=dict(size=15, family=BOLD_FONT, color="#000"),
|
| 417 |
side="top", range=[xmin, xmax],
|
| 418 |
-
ticks="outside",
|
|
|
|
|
|
|
| 419 |
showline=True, linewidth=1.2, linecolor="#444", mirror=True,
|
| 420 |
showgrid=True, gridcolor="rgba(0,0,0,0.12)", automargin=True
|
| 421 |
)
|
|
@@ -496,8 +498,11 @@ st.session_state.setdefault("dev_file_name","")
|
|
| 496 |
st.session_state.setdefault("dev_file_bytes",b"")
|
| 497 |
st.session_state.setdefault("dev_file_loaded",False)
|
| 498 |
st.session_state.setdefault("dev_preview",False)
|
| 499 |
-
st.session_state.setdefault("
|
| 500 |
-
|
|
|
|
|
|
|
|
|
|
| 501 |
|
| 502 |
# =========================
|
| 503 |
# Sidebar branding
|
|
@@ -527,6 +532,50 @@ def sticky_header(title, message):
|
|
| 527 |
unsafe_allow_html=True
|
| 528 |
)
|
| 529 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 530 |
# =========================
|
| 531 |
# INTRO
|
| 532 |
# =========================
|
|
@@ -567,21 +616,26 @@ if st.session_state.app_step == "dev":
|
|
| 567 |
df0 = next(iter(tmp.values()))
|
| 568 |
st.sidebar.caption(f"**Data loaded:** {st.session_state.dev_file_name} • {df0.shape[0]} rows × {df0.shape[1]} cols")
|
| 569 |
|
|
|
|
| 570 |
if st.sidebar.button("Preview data", use_container_width=True, disabled=not st.session_state.dev_file_loaded):
|
| 571 |
-
st.session_state.
|
| 572 |
-
st.session_state.
|
|
|
|
| 573 |
|
| 574 |
run = st.sidebar.button("Run Model", type="primary", use_container_width=True)
|
| 575 |
if st.sidebar.button("Proceed to Validation ▶", use_container_width=True): st.session_state.app_step="validate"; st.rerun()
|
| 576 |
if st.sidebar.button("Proceed to Prediction ▶", use_container_width=True): st.session_state.app_step="predict"; st.rerun()
|
| 577 |
|
| 578 |
-
if st.session_state.dev_file_loaded and st.session_state.
|
| 579 |
sticky_header("Case Building", "Previewed ✓ — now click **Run Model**.")
|
| 580 |
elif st.session_state.dev_file_loaded:
|
| 581 |
sticky_header("Case Building", "📄 **Preview uploaded data** using the sidebar button, then click **Run Model**.")
|
| 582 |
else:
|
| 583 |
sticky_header("Case Building", "**Upload your data to build a case, then run the model to review performance.**")
|
| 584 |
|
|
|
|
|
|
|
|
|
|
| 585 |
if run and st.session_state.dev_file_bytes:
|
| 586 |
book = read_book_bytes(st.session_state.dev_file_bytes)
|
| 587 |
sh_train = _find_sheet(book, ["Train","Training","training2","train","training"])
|
|
@@ -674,13 +728,19 @@ if st.session_state.app_step == "validate":
|
|
| 674 |
if book:
|
| 675 |
df0 = next(iter(book.values()))
|
| 676 |
st.sidebar.caption(f"**Data loaded:** {up.name} • {df0.shape[0]} rows × {df0.shape[1]} cols")
|
|
|
|
|
|
|
| 677 |
if st.sidebar.button("Preview data", use_container_width=True, disabled=(up is None)):
|
| 678 |
-
st.session_state.
|
|
|
|
|
|
|
|
|
|
| 679 |
go_btn = st.sidebar.button("Predict & Validate", type="primary", use_container_width=True)
|
| 680 |
if st.sidebar.button("⬅ Back to Case Building", use_container_width=True): st.session_state.app_step="dev"; st.rerun()
|
| 681 |
if st.sidebar.button("Proceed to Prediction ▶", use_container_width=True): st.session_state.app_step="predict"; st.rerun()
|
| 682 |
|
| 683 |
sticky_header("Validate the Model", "Upload a dataset with the same **features** and **MINStress_Actual** to evaluate performance.")
|
|
|
|
| 684 |
|
| 685 |
if go_btn and up is not None:
|
| 686 |
if st.session_state.fitted_model is None:
|
|
@@ -759,12 +819,18 @@ if st.session_state.app_step == "predict":
|
|
| 759 |
if book:
|
| 760 |
df0 = next(iter(book.values()))
|
| 761 |
st.sidebar.caption(f"**Data loaded:** {up.name} • {df0.shape[0]} rows × {df0.shape[1]} cols")
|
|
|
|
|
|
|
| 762 |
if st.sidebar.button("Preview data", use_container_width=True, disabled=(up is None)):
|
| 763 |
-
st.session_state.
|
|
|
|
|
|
|
|
|
|
| 764 |
go_btn = st.sidebar.button("Predict", type="primary", use_container_width=True)
|
| 765 |
if st.sidebar.button("⬅ Back to Case Building", use_container_width=True): st.session_state.app_step="dev"; st.rerun()
|
| 766 |
|
| 767 |
sticky_header("Prediction", "Upload a dataset with the 5 feature columns (no actual column).")
|
|
|
|
| 768 |
|
| 769 |
if go_btn and up is not None:
|
| 770 |
if st.session_state.fitted_model is None:
|
|
@@ -809,43 +875,6 @@ if st.session_state.app_step == "predict":
|
|
| 809 |
use_container_width=False, config={"displayModeBar": False, "scrollZoom": True})
|
| 810 |
render_export_button(phase_key="predict")
|
| 811 |
|
| 812 |
-
# =========================
|
| 813 |
-
# Preview modal
|
| 814 |
-
# =========================
|
| 815 |
-
if st.session_state.show_preview_modal:
|
| 816 |
-
book_to_preview = {}
|
| 817 |
-
if st.session_state.app_step == "dev":
|
| 818 |
-
book_to_preview = read_book_bytes(st.session_state.dev_file_bytes)
|
| 819 |
-
elif st.session_state.app_step in ["validate", "predict"] and 'up' in locals() and up is not None:
|
| 820 |
-
book_to_preview = read_book_bytes(up.getvalue())
|
| 821 |
-
|
| 822 |
-
with st.expander("Preview data", expanded=True):
|
| 823 |
-
if not book_to_preview:
|
| 824 |
-
st.markdown('<div class="st-message-box">No data loaded yet.</div>', unsafe_allow_html=True)
|
| 825 |
-
else:
|
| 826 |
-
names = list(book_to_preview.keys())
|
| 827 |
-
tabs = st.tabs(names)
|
| 828 |
-
for t, name in zip(tabs, names):
|
| 829 |
-
with t:
|
| 830 |
-
df = book_to_preview[name]
|
| 831 |
-
t1, t2 = st.tabs(["Tracks", "Summary"])
|
| 832 |
-
with t1:
|
| 833 |
-
st.pyplot(preview_tracks(df, FEATURES), use_container_width=True)
|
| 834 |
-
with t2:
|
| 835 |
-
feat_present = [c for c in FEATURES if c in df.columns]
|
| 836 |
-
if not feat_present:
|
| 837 |
-
st.info("No feature columns found to summarize.")
|
| 838 |
-
else:
|
| 839 |
-
tbl = (
|
| 840 |
-
df[feat_present]
|
| 841 |
-
.agg(['min','max','mean','std'])
|
| 842 |
-
.T.rename(columns={"min":"Min","max":"Max","mean":"Mean","std":"Std"})
|
| 843 |
-
.reset_index(names="Feature")
|
| 844 |
-
)
|
| 845 |
-
df_centered_rounded(tbl)
|
| 846 |
-
|
| 847 |
-
st.session_state.show_preview_modal = False
|
| 848 |
-
|
| 849 |
# =========================
|
| 850 |
# Footer
|
| 851 |
# =========================
|
|
|
|
| 29 |
APP_NAME = "ST_Min_Horizontal_Stress"
|
| 30 |
TAGLINE = "Real-Time Minimum Horizontal Stress Prediction"
|
| 31 |
|
| 32 |
+
# -------- Canonical names (match your files) --------
|
| 33 |
FEATURES = ["Q (gpm)", "SPP (psi)", "T (kft.lbf)", "WOB (klbf)", "ROP (ft/h)"]
|
| 34 |
TARGET = "MINStress_Actual"
|
| 35 |
PRED_COL = "MINStress_Pred"
|
| 36 |
ACTUAL_COL = TARGET
|
| 37 |
TRANSFORM = "none" # "none" | "log10" | "ln"
|
| 38 |
+
UNITS = "Psi"
|
| 39 |
|
| 40 |
+
# ---- Fixed ("best") model params baked into the code ----
|
| 41 |
BEST_PARAMS = dict(
|
| 42 |
n_estimators=400,
|
| 43 |
max_depth=None,
|
|
|
|
| 354 |
ax.set_xticks(ticks); ax.set_yticks(ticks)
|
| 355 |
ax.set_aspect("equal", adjustable="box")
|
| 356 |
|
| 357 |
+
fmt = FuncFormatter(lambda x, _: f"{x:.0f}") # no decimals on cross-plot
|
| 358 |
ax.xaxis.set_major_formatter(fmt); ax.yaxis.set_major_formatter(fmt)
|
| 359 |
|
| 360 |
ax.set_xlabel(f"Actual Min Stress ({UNITS})", fontweight="bold", fontsize=10, color="black")
|
|
|
|
| 415 |
title_font=dict(size=20, family=BOLD_FONT, color="#000"),
|
| 416 |
tickfont=dict(size=15, family=BOLD_FONT, color="#000"),
|
| 417 |
side="top", range=[xmin, xmax],
|
| 418 |
+
ticks="outside",
|
| 419 |
+
tickformat=",.0f", # <— no decimals on ticks
|
| 420 |
+
tickmode="auto", tick0=tick0,
|
| 421 |
showline=True, linewidth=1.2, linecolor="#444", mirror=True,
|
| 422 |
showgrid=True, gridcolor="rgba(0,0,0,0.12)", automargin=True
|
| 423 |
)
|
|
|
|
| 498 |
st.session_state.setdefault("dev_file_bytes",b"")
|
| 499 |
st.session_state.setdefault("dev_file_loaded",False)
|
| 500 |
st.session_state.setdefault("dev_preview",False)
|
| 501 |
+
st.session_state.setdefault("fitted_model", None) # cache trained pipeline
|
| 502 |
+
|
| 503 |
+
# NEW: persistent top-of-page preview panel state
|
| 504 |
+
st.session_state.setdefault("show_preview_panel", False)
|
| 505 |
+
st.session_state.setdefault("preview_book", {}) # parsed Excel sheets to preview
|
| 506 |
|
| 507 |
# =========================
|
| 508 |
# Sidebar branding
|
|
|
|
| 532 |
unsafe_allow_html=True
|
| 533 |
)
|
| 534 |
|
| 535 |
+
# ---------- Top-of-page Preview Panel ----------
|
| 536 |
+
def render_preview_panel():
|
| 537 |
+
"""If enabled, draws a preview panel at the very top of the page."""
|
| 538 |
+
if not st.session_state.get("show_preview_panel"):
|
| 539 |
+
return
|
| 540 |
+
|
| 541 |
+
st.markdown("## 🔎 Data preview")
|
| 542 |
+
book = st.session_state.get("preview_book", {}) or {}
|
| 543 |
+
if not book:
|
| 544 |
+
st.info("No data loaded yet.")
|
| 545 |
+
col = st.columns(2)[1]
|
| 546 |
+
with col:
|
| 547 |
+
if st.button("Hide preview"):
|
| 548 |
+
st.session_state.show_preview_panel = False
|
| 549 |
+
st.session_state.preview_book = {}
|
| 550 |
+
st.rerun()
|
| 551 |
+
return
|
| 552 |
+
|
| 553 |
+
names = list(book.keys())
|
| 554 |
+
tabs = st.tabs(names + ["✖ Hide preview"])
|
| 555 |
+
for i, name in enumerate(names):
|
| 556 |
+
with tabs[i]:
|
| 557 |
+
df = book[name]
|
| 558 |
+
t1, t2 = st.tabs(["Tracks", "Summary"])
|
| 559 |
+
with t1:
|
| 560 |
+
st.pyplot(preview_tracks(df, FEATURES), use_container_width=True)
|
| 561 |
+
with t2:
|
| 562 |
+
feat_present = [c for c in FEATURES if c in df.columns]
|
| 563 |
+
if not feat_present:
|
| 564 |
+
st.info("No feature columns found to summarize.")
|
| 565 |
+
else:
|
| 566 |
+
tbl = (
|
| 567 |
+
df[feat_present]
|
| 568 |
+
.agg(['min','max','mean','std'])
|
| 569 |
+
.T.rename(columns={"min":"Min","max":"Max","mean":"Mean","std":"Std"})
|
| 570 |
+
.reset_index(names="Feature")
|
| 571 |
+
)
|
| 572 |
+
df_centered_rounded(tbl)
|
| 573 |
+
with tabs[-1]:
|
| 574 |
+
if st.button("Hide preview", use_container_width=True):
|
| 575 |
+
st.session_state.show_preview_panel = False
|
| 576 |
+
st.session_state.preview_book = {}
|
| 577 |
+
st.rerun()
|
| 578 |
+
|
| 579 |
# =========================
|
| 580 |
# INTRO
|
| 581 |
# =========================
|
|
|
|
| 616 |
df0 = next(iter(tmp.values()))
|
| 617 |
st.sidebar.caption(f"**Data loaded:** {st.session_state.dev_file_name} • {df0.shape[0]} rows × {df0.shape[1]} cols")
|
| 618 |
|
| 619 |
+
# PREVIEW button -> show preview panel at top
|
| 620 |
if st.sidebar.button("Preview data", use_container_width=True, disabled=not st.session_state.dev_file_loaded):
|
| 621 |
+
st.session_state.preview_book = read_book_bytes(st.session_state.dev_file_bytes) if st.session_state.dev_file_bytes else {}
|
| 622 |
+
st.session_state.show_preview_panel = True
|
| 623 |
+
st.rerun()
|
| 624 |
|
| 625 |
run = st.sidebar.button("Run Model", type="primary", use_container_width=True)
|
| 626 |
if st.sidebar.button("Proceed to Validation ▶", use_container_width=True): st.session_state.app_step="validate"; st.rerun()
|
| 627 |
if st.sidebar.button("Proceed to Prediction ▶", use_container_width=True): st.session_state.app_step="predict"; st.rerun()
|
| 628 |
|
| 629 |
+
if st.session_state.dev_file_loaded and st.session_state.show_preview_panel:
|
| 630 |
sticky_header("Case Building", "Previewed ✓ — now click **Run Model**.")
|
| 631 |
elif st.session_state.dev_file_loaded:
|
| 632 |
sticky_header("Case Building", "📄 **Preview uploaded data** using the sidebar button, then click **Run Model**.")
|
| 633 |
else:
|
| 634 |
sticky_header("Case Building", "**Upload your data to build a case, then run the model to review performance.**")
|
| 635 |
|
| 636 |
+
# Render the preview panel at the very top (above results)
|
| 637 |
+
render_preview_panel()
|
| 638 |
+
|
| 639 |
if run and st.session_state.dev_file_bytes:
|
| 640 |
book = read_book_bytes(st.session_state.dev_file_bytes)
|
| 641 |
sh_train = _find_sheet(book, ["Train","Training","training2","train","training"])
|
|
|
|
| 728 |
if book:
|
| 729 |
df0 = next(iter(book.values()))
|
| 730 |
st.sidebar.caption(f"**Data loaded:** {up.name} • {df0.shape[0]} rows × {df0.shape[1]} cols")
|
| 731 |
+
|
| 732 |
+
# PREVIEW button -> show preview panel at top
|
| 733 |
if st.sidebar.button("Preview data", use_container_width=True, disabled=(up is None)):
|
| 734 |
+
st.session_state.preview_book = read_book_bytes(up.getvalue()) if up is not None else {}
|
| 735 |
+
st.session_state.show_preview_panel = True
|
| 736 |
+
st.rerun()
|
| 737 |
+
|
| 738 |
go_btn = st.sidebar.button("Predict & Validate", type="primary", use_container_width=True)
|
| 739 |
if st.sidebar.button("⬅ Back to Case Building", use_container_width=True): st.session_state.app_step="dev"; st.rerun()
|
| 740 |
if st.sidebar.button("Proceed to Prediction ▶", use_container_width=True): st.session_state.app_step="predict"; st.rerun()
|
| 741 |
|
| 742 |
sticky_header("Validate the Model", "Upload a dataset with the same **features** and **MINStress_Actual** to evaluate performance.")
|
| 743 |
+
render_preview_panel() # top-of-page preview
|
| 744 |
|
| 745 |
if go_btn and up is not None:
|
| 746 |
if st.session_state.fitted_model is None:
|
|
|
|
| 819 |
if book:
|
| 820 |
df0 = next(iter(book.values()))
|
| 821 |
st.sidebar.caption(f"**Data loaded:** {up.name} • {df0.shape[0]} rows × {df0.shape[1]} cols")
|
| 822 |
+
|
| 823 |
+
# PREVIEW button -> show preview panel at top
|
| 824 |
if st.sidebar.button("Preview data", use_container_width=True, disabled=(up is None)):
|
| 825 |
+
st.session_state.preview_book = read_book_bytes(up.getvalue()) if up is not None else {}
|
| 826 |
+
st.session_state.show_preview_panel = True
|
| 827 |
+
st.rerun()
|
| 828 |
+
|
| 829 |
go_btn = st.sidebar.button("Predict", type="primary", use_container_width=True)
|
| 830 |
if st.sidebar.button("⬅ Back to Case Building", use_container_width=True): st.session_state.app_step="dev"; st.rerun()
|
| 831 |
|
| 832 |
sticky_header("Prediction", "Upload a dataset with the 5 feature columns (no actual column).")
|
| 833 |
+
render_preview_panel() # top-of-page preview
|
| 834 |
|
| 835 |
if go_btn and up is not None:
|
| 836 |
if st.session_state.fitted_model is None:
|
|
|
|
| 875 |
use_container_width=False, config={"displayModeBar": False, "scrollZoom": True})
|
| 876 |
render_export_button(phase_key="predict")
|
| 877 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 878 |
# =========================
|
| 879 |
# Footer
|
| 880 |
# =========================
|