Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# app.py —
|
| 2 |
import io, json, os, base64, math
|
| 3 |
from pathlib import Path
|
| 4 |
import streamlit as st
|
|
@@ -17,17 +17,18 @@ import plotly.graph_objects as go
|
|
| 17 |
from sklearn.metrics import mean_squared_error, mean_absolute_error
|
| 18 |
|
| 19 |
# =========================
|
| 20 |
-
# Constants (
|
| 21 |
# =========================
|
| 22 |
-
APP_NAME = "
|
| 23 |
-
TAGLINE = "Real-Time
|
| 24 |
|
|
|
|
| 25 |
FEATURES = ["WOB(klbf)", "TORQUE(kft.lbf)", "SPP(psi)", "RPM(1/min)", "ROP(ft/h)", "Flow Rate, gpm"]
|
| 26 |
-
TARGET = "
|
| 27 |
-
PRED_COL = "
|
| 28 |
|
| 29 |
MODELS_DIR = Path("models")
|
| 30 |
-
DEFAULT_MODEL = MODELS_DIR / "
|
| 31 |
MODEL_FALLBACKS = [MODELS_DIR / "model.joblib", MODELS_DIR / "model.pkl"]
|
| 32 |
COLORS = {"pred": "#1f77b4", "actual": "#f2b702", "ref": "#5a5a5a"}
|
| 33 |
|
|
@@ -193,7 +194,7 @@ def find_sheet(book, names):
|
|
| 193 |
return None
|
| 194 |
|
| 195 |
def _nice_tick0(xmin: float, step: float = 0.1) -> float:
|
| 196 |
-
# Rounded start tick for continuous
|
| 197 |
return step * math.floor(xmin / step) if np.isfinite(xmin) else xmin
|
| 198 |
|
| 199 |
def df_centered_rounded(df: pd.DataFrame, hide_index=True):
|
|
@@ -349,7 +350,7 @@ def build_export_workbook(selected: list[str], ndigits: int = 3, do_autofit: boo
|
|
| 349 |
_excel_autofit(writer, sheet, df)
|
| 350 |
bio.seek(0)
|
| 351 |
|
| 352 |
-
fname = f"
|
| 353 |
return bio.getvalue(), fname, order
|
| 354 |
|
| 355 |
# --------- SIMPLE export UI (dropdown checklist, starts empty) ----------
|
|
@@ -380,7 +381,7 @@ def render_export_button(phase_key: str) -> None:
|
|
| 380 |
st.download_button(
|
| 381 |
label="⬇️ Export Excel",
|
| 382 |
data=b"",
|
| 383 |
-
file_name="
|
| 384 |
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
| 385 |
disabled=True,
|
| 386 |
key=f"download_{phase_key}",
|
|
@@ -394,16 +395,16 @@ def render_export_button(phase_key: str) -> None:
|
|
| 394 |
st.download_button(
|
| 395 |
"⬇️ Export Excel",
|
| 396 |
data=(data or b""),
|
| 397 |
-
file_name=(fname or "
|
| 398 |
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
| 399 |
disabled=(data is None),
|
| 400 |
key=f"download_{phase_key}",
|
| 401 |
)
|
| 402 |
|
| 403 |
# =========================
|
| 404 |
-
# Cross plot (Matplotlib) — auto-scaled for
|
| 405 |
# =========================
|
| 406 |
-
def cross_plot_static(actual, pred, xlabel="Actual
|
| 407 |
a = pd.Series(actual, dtype=float)
|
| 408 |
p = pd.Series(pred, dtype=float)
|
| 409 |
|
|
@@ -426,7 +427,7 @@ def cross_plot_static(actual, pred, xlabel="Actual Ym", ylabel="Predicted Ym"):
|
|
| 426 |
ax.set_yticks(ticks)
|
| 427 |
ax.set_aspect("equal", adjustable="box")
|
| 428 |
|
| 429 |
-
# Generic numeric formatting (2 decimals)
|
| 430 |
fmt = FuncFormatter(lambda x, _: f"{x:.2f}")
|
| 431 |
ax.xaxis.set_major_formatter(fmt)
|
| 432 |
ax.yaxis.set_major_formatter(fmt)
|
|
@@ -495,15 +496,15 @@ def track_plot(df, include_actual=True):
|
|
| 495 |
legend_title_text=""
|
| 496 |
)
|
| 497 |
|
| 498 |
-
# X axis with NO decimals
|
| 499 |
fig.update_xaxes(
|
| 500 |
-
title_text="
|
| 501 |
title_font=dict(size=20, family=BOLD_FONT, color="#000"),
|
| 502 |
tickfont=dict(size=15, family=BOLD_FONT, color="#000"),
|
| 503 |
side="top",
|
| 504 |
range=[xmin, xmax],
|
| 505 |
ticks="outside",
|
| 506 |
-
tickformat=",.0f",
|
| 507 |
tickmode="auto",
|
| 508 |
tick0=tick0,
|
| 509 |
showline=True, linewidth=1.2, linecolor="#444", mirror=True,
|
|
@@ -598,7 +599,7 @@ def ensure_model() -> Path|None:
|
|
| 598 |
|
| 599 |
mpath = ensure_model()
|
| 600 |
if not mpath:
|
| 601 |
-
st.error("Model not found. Upload models/
|
| 602 |
st.stop()
|
| 603 |
try:
|
| 604 |
model = load_model(str(mpath))
|
|
@@ -608,13 +609,16 @@ except Exception as e:
|
|
| 608 |
|
| 609 |
# ---------- Load meta (optional) ----------
|
| 610 |
meta = {}
|
| 611 |
-
|
|
|
|
| 612 |
meta_path = next((p for p in meta_candidates if p.exists()), None)
|
| 613 |
if meta_path:
|
| 614 |
try:
|
| 615 |
meta = json.loads(meta_path.read_text(encoding="utf-8"))
|
| 616 |
FEATURES = meta.get("features", FEATURES)
|
| 617 |
TARGET = meta.get("target", TARGET)
|
|
|
|
|
|
|
| 618 |
except Exception as e:
|
| 619 |
st.warning(f"Could not parse meta file ({meta_path.name}): {e}")
|
| 620 |
|
|
@@ -679,12 +683,12 @@ def sticky_header(title, message):
|
|
| 679 |
# =========================
|
| 680 |
if st.session_state.app_step == "intro":
|
| 681 |
st.header("Welcome!")
|
| 682 |
-
st.markdown("This software is developed by *Smart Thinking AI-Solutions Team* to estimate
|
| 683 |
st.subheader("How It Works")
|
| 684 |
st.markdown(
|
| 685 |
"1) **Upload your data to build the case and preview the model performance.** \n"
|
| 686 |
"2) Click **Run Model** to compute metrics and plots. \n"
|
| 687 |
-
"3) **Proceed to Validation** (with actual
|
| 688 |
)
|
| 689 |
if st.button("Start Showcase", type="primary"):
|
| 690 |
st.session_state.app_step = "dev"; st.rerun()
|
|
@@ -791,7 +795,7 @@ if st.session_state.app_step == "dev":
|
|
| 791 |
render_export_button(phase_key="dev")
|
| 792 |
|
| 793 |
# =========================
|
| 794 |
-
# VALIDATION (with actual
|
| 795 |
# =========================
|
| 796 |
if st.session_state.app_step == "validate":
|
| 797 |
st.sidebar.header("Validate the Model")
|
|
@@ -807,7 +811,7 @@ if st.session_state.app_step == "validate":
|
|
| 807 |
if st.sidebar.button("⬅ Back to Case Building", use_container_width=True): st.session_state.app_step="dev"; st.rerun()
|
| 808 |
if st.sidebar.button("Proceed to Prediction ▶", use_container_width=True): st.session_state.app_step="predict"; st.rerun()
|
| 809 |
|
| 810 |
-
sticky_header("Validate the Model", "Upload a dataset with the same **features** and **
|
| 811 |
|
| 812 |
if go_btn and up is not None:
|
| 813 |
book = read_book_bytes(up.getvalue())
|
|
@@ -874,10 +878,10 @@ if st.session_state.app_step == "validate":
|
|
| 874 |
df_centered_rounded(st.session_state.results["oor_tbl"])
|
| 875 |
|
| 876 |
# =========================
|
| 877 |
-
# PREDICTION (no actual
|
| 878 |
# =========================
|
| 879 |
if st.session_state.app_step == "predict":
|
| 880 |
-
st.sidebar.header("Prediction (No Actual
|
| 881 |
up = st.sidebar.file_uploader("Upload Prediction Excel", type=["xlsx","xls"])
|
| 882 |
if up is not None:
|
| 883 |
book = read_book_bytes(up.getvalue())
|
|
@@ -889,7 +893,7 @@ if st.session_state.app_step == "predict":
|
|
| 889 |
go_btn = st.sidebar.button("Predict", type="primary", use_container_width=True)
|
| 890 |
if st.sidebar.button("⬅ Back to Case Building", use_container_width=True): st.session_state.app_step="dev"; st.rerun()
|
| 891 |
|
| 892 |
-
sticky_header("Prediction", "Upload a dataset with the feature columns (no **
|
| 893 |
|
| 894 |
if go_btn and up is not None:
|
| 895 |
book = read_book_bytes(up.getvalue()); name = list(book.keys())[0]
|
|
|
|
| 1 |
+
# app.py — ST_Sonic_Ts (mirrors your Ym GUI, adapted for Shear Slowness Ts)
|
| 2 |
import io, json, os, base64, math
|
| 3 |
from pathlib import Path
|
| 4 |
import streamlit as st
|
|
|
|
| 17 |
from sklearn.metrics import mean_squared_error, mean_absolute_error
|
| 18 |
|
| 19 |
# =========================
|
| 20 |
+
# Constants (Ts variant)
|
| 21 |
# =========================
|
| 22 |
+
APP_NAME = "ST_Sonic_Ts"
|
| 23 |
+
TAGLINE = "Real-Time Shear Slowness (Ts) Tracking"
|
| 24 |
|
| 25 |
+
# Keep your drilling features the same (match training & Excel headers)
|
| 26 |
FEATURES = ["WOB(klbf)", "TORQUE(kft.lbf)", "SPP(psi)", "RPM(1/min)", "ROP(ft/h)", "Flow Rate, gpm"]
|
| 27 |
+
TARGET = "Ts" # <-- actual Ts column in your sheets
|
| 28 |
+
PRED_COL = "Ts_Pred" # <-- predicted Ts column added by the app
|
| 29 |
|
| 30 |
MODELS_DIR = Path("models")
|
| 31 |
+
DEFAULT_MODEL = MODELS_DIR / "ts_model.joblib"
|
| 32 |
MODEL_FALLBACKS = [MODELS_DIR / "model.joblib", MODELS_DIR / "model.pkl"]
|
| 33 |
COLORS = {"pred": "#1f77b4", "actual": "#f2b702", "ref": "#5a5a5a"}
|
| 34 |
|
|
|
|
| 194 |
return None
|
| 195 |
|
| 196 |
def _nice_tick0(xmin: float, step: float = 0.1) -> float:
|
| 197 |
+
# Rounded start tick for continuous Ts scales (unit-agnostic)
|
| 198 |
return step * math.floor(xmin / step) if np.isfinite(xmin) else xmin
|
| 199 |
|
| 200 |
def df_centered_rounded(df: pd.DataFrame, hide_index=True):
|
|
|
|
| 350 |
_excel_autofit(writer, sheet, df)
|
| 351 |
bio.seek(0)
|
| 352 |
|
| 353 |
+
fname = f"TS_Export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
|
| 354 |
return bio.getvalue(), fname, order
|
| 355 |
|
| 356 |
# --------- SIMPLE export UI (dropdown checklist, starts empty) ----------
|
|
|
|
| 381 |
st.download_button(
|
| 382 |
label="⬇️ Export Excel",
|
| 383 |
data=b"",
|
| 384 |
+
file_name="TS_Export.xlsx",
|
| 385 |
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
| 386 |
disabled=True,
|
| 387 |
key=f"download_{phase_key}",
|
|
|
|
| 395 |
st.download_button(
|
| 396 |
"⬇️ Export Excel",
|
| 397 |
data=(data or b""),
|
| 398 |
+
file_name=(fname or "TS_Export.xlsx"),
|
| 399 |
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
| 400 |
disabled=(data is None),
|
| 401 |
key=f"download_{phase_key}",
|
| 402 |
)
|
| 403 |
|
| 404 |
# =========================
|
| 405 |
+
# Cross plot (Matplotlib) — auto-scaled for Ts
|
| 406 |
# =========================
|
| 407 |
+
def cross_plot_static(actual, pred, xlabel="Actual Ts (µs/ft)", ylabel="Predicted Ts (µs/ft)"):
|
| 408 |
a = pd.Series(actual, dtype=float)
|
| 409 |
p = pd.Series(pred, dtype=float)
|
| 410 |
|
|
|
|
| 427 |
ax.set_yticks(ticks)
|
| 428 |
ax.set_aspect("equal", adjustable="box")
|
| 429 |
|
| 430 |
+
# Generic numeric formatting (2 decimals) in plot; export uses 3 decimals
|
| 431 |
fmt = FuncFormatter(lambda x, _: f"{x:.2f}")
|
| 432 |
ax.xaxis.set_major_formatter(fmt)
|
| 433 |
ax.yaxis.set_major_formatter(fmt)
|
|
|
|
| 496 |
legend_title_text=""
|
| 497 |
)
|
| 498 |
|
| 499 |
+
# X axis with NO decimals (Ts is in µs/ft; typically integer-like)
|
| 500 |
fig.update_xaxes(
|
| 501 |
+
title_text="Ts (µs/ft)",
|
| 502 |
title_font=dict(size=20, family=BOLD_FONT, color="#000"),
|
| 503 |
tickfont=dict(size=15, family=BOLD_FONT, color="#000"),
|
| 504 |
side="top",
|
| 505 |
range=[xmin, xmax],
|
| 506 |
ticks="outside",
|
| 507 |
+
tickformat=",.0f",
|
| 508 |
tickmode="auto",
|
| 509 |
tick0=tick0,
|
| 510 |
showline=True, linewidth=1.2, linecolor="#444", mirror=True,
|
|
|
|
| 599 |
|
| 600 |
mpath = ensure_model()
|
| 601 |
if not mpath:
|
| 602 |
+
st.error("Model not found. Upload models/ts_model.joblib (or set MODEL_URL).")
|
| 603 |
st.stop()
|
| 604 |
try:
|
| 605 |
model = load_model(str(mpath))
|
|
|
|
| 609 |
|
| 610 |
# ---------- Load meta (optional) ----------
|
| 611 |
meta = {}
|
| 612 |
+
# Prefer a Ts-specific meta, fall back to a generic one if present
|
| 613 |
+
meta_candidates = [MODELS_DIR / "ts_meta.json", MODELS_DIR / "meta.json"]
|
| 614 |
meta_path = next((p for p in meta_candidates if p.exists()), None)
|
| 615 |
if meta_path:
|
| 616 |
try:
|
| 617 |
meta = json.loads(meta_path.read_text(encoding="utf-8"))
|
| 618 |
FEATURES = meta.get("features", FEATURES)
|
| 619 |
TARGET = meta.get("target", TARGET)
|
| 620 |
+
# If meta provides a custom pred column, respect it
|
| 621 |
+
PRED_COL = meta.get("pred_col", PRED_COL)
|
| 622 |
except Exception as e:
|
| 623 |
st.warning(f"Could not parse meta file ({meta_path.name}): {e}")
|
| 624 |
|
|
|
|
| 683 |
# =========================
|
| 684 |
if st.session_state.app_step == "intro":
|
| 685 |
st.header("Welcome!")
|
| 686 |
+
st.markdown("This software is developed by *Smart Thinking AI-Solutions Team* to estimate **Shear Slowness (Ts)** from drilling data.")
|
| 687 |
st.subheader("How It Works")
|
| 688 |
st.markdown(
|
| 689 |
"1) **Upload your data to build the case and preview the model performance.** \n"
|
| 690 |
"2) Click **Run Model** to compute metrics and plots. \n"
|
| 691 |
+
"3) **Proceed to Validation** (with actual Ts) or **Proceed to Prediction** (no Ts)."
|
| 692 |
)
|
| 693 |
if st.button("Start Showcase", type="primary"):
|
| 694 |
st.session_state.app_step = "dev"; st.rerun()
|
|
|
|
| 795 |
render_export_button(phase_key="dev")
|
| 796 |
|
| 797 |
# =========================
|
| 798 |
+
# VALIDATION (with actual Ts)
|
| 799 |
# =========================
|
| 800 |
if st.session_state.app_step == "validate":
|
| 801 |
st.sidebar.header("Validate the Model")
|
|
|
|
| 811 |
if st.sidebar.button("⬅ Back to Case Building", use_container_width=True): st.session_state.app_step="dev"; st.rerun()
|
| 812 |
if st.sidebar.button("Proceed to Prediction ▶", use_container_width=True): st.session_state.app_step="predict"; st.rerun()
|
| 813 |
|
| 814 |
+
sticky_header("Validate the Model", "Upload a dataset with the same **features** and **Ts** to evaluate performance.")
|
| 815 |
|
| 816 |
if go_btn and up is not None:
|
| 817 |
book = read_book_bytes(up.getvalue())
|
|
|
|
| 878 |
df_centered_rounded(st.session_state.results["oor_tbl"])
|
| 879 |
|
| 880 |
# =========================
|
| 881 |
+
# PREDICTION (no actual Ts)
|
| 882 |
# =========================
|
| 883 |
if st.session_state.app_step == "predict":
|
| 884 |
+
st.sidebar.header("Prediction (No Actual Ts)")
|
| 885 |
up = st.sidebar.file_uploader("Upload Prediction Excel", type=["xlsx","xls"])
|
| 886 |
if up is not None:
|
| 887 |
book = read_book_bytes(up.getvalue())
|
|
|
|
| 893 |
go_btn = st.sidebar.button("Predict", type="primary", use_container_width=True)
|
| 894 |
if st.sidebar.button("⬅ Back to Case Building", use_container_width=True): st.session_state.app_step="dev"; st.rerun()
|
| 895 |
|
| 896 |
+
sticky_header("Prediction", "Upload a dataset with the feature columns (no **Ts**).")
|
| 897 |
|
| 898 |
if go_btn and up is not None:
|
| 899 |
book = read_book_bytes(up.getvalue()); name = list(book.keys())[0]
|