Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
# app.py — ST_GeoMech_SMW
|
| 2 |
-
|
| 3 |
import io, json, os, base64, math
|
| 4 |
from pathlib import Path
|
| 5 |
import streamlit as st
|
|
@@ -23,7 +22,7 @@ from sklearn.metrics import mean_squared_error, mean_absolute_error
|
|
| 23 |
APP_NAME = "ST_GeoMech_SMW"
|
| 24 |
TAGLINE = "Real-Time Upper/Lower Mud Weight (MW) Limits For Safe Drilling"
|
| 25 |
|
| 26 |
-
# Defaults (can be overridden by
|
| 27 |
FEATURES_DEFAULT = [
|
| 28 |
"WOB (klbf)",
|
| 29 |
"Torque (kft.lbf)",
|
|
@@ -45,7 +44,6 @@ BD_MODEL_PATH = MODELS_DIR / "bd_model.joblib"
|
|
| 45 |
BO_META_PATH = MODELS_DIR / "bo_meta.json"
|
| 46 |
BD_META_PATH = MODELS_DIR / "bd_meta.json"
|
| 47 |
|
| 48 |
-
MODEL_FALLBACKS = [MODELS_DIR / "model.joblib", MODELS_DIR / "model.pkl"]
|
| 49 |
COLORS = {
|
| 50 |
"pred_bo": "#1f77b4", # blue
|
| 51 |
"pred_bd": "#d62728", # red
|
|
@@ -197,10 +195,10 @@ def _build_alias_map(canonical_features: list[str], tgt_bo: str, tgt_bd: str) ->
|
|
| 197 |
can_ROP = pick(canonical_features, ["ROP (ft/h)", "ROP(ft/h)"])
|
| 198 |
can_FR = pick(canonical_features, ["Flow Rate (gpm)", "Flow Rate, gpm", "Flow Rate,gpm", "Flow Rate , gpm"])
|
| 199 |
|
| 200 |
-
#
|
| 201 |
can_DEPTH = pick(canonical_features, ["Depth", "Depth (ft)", "Depth, ft", "Depth(ft)", "DEPTH, ft"])
|
| 202 |
|
| 203 |
-
|
| 204 |
"WOB (klbf)": can_WOB, "WOB, klbf": can_WOB, "WOB(klbf)": can_WOB, "WOB( klbf)": can_WOB,
|
| 205 |
"Torque (kft.lbf)": can_TORQUE, "Torque(kft.lbf)": can_TORQUE, "TORQUE(kft.lbf)": can_TORQUE,
|
| 206 |
"SPP (psi)": can_SPP, "SPP(psi)": can_SPP,
|
|
@@ -208,15 +206,15 @@ def _build_alias_map(canonical_features: list[str], tgt_bo: str, tgt_bd: str) ->
|
|
| 208 |
"ROP (ft/h)": can_ROP, "ROP(ft/h)": can_ROP,
|
| 209 |
"Flow Rate (gpm)": can_FR, "Flow Rate, gpm": can_FR, "Flow Rate,gpm": can_FR, "Flow Rate , gpm": can_FR,
|
| 210 |
|
| 211 |
-
# Depth aliases
|
| 212 |
"Depth": can_DEPTH, "Depth (ft)": can_DEPTH, "Depth, ft": can_DEPTH, "Depth(ft)": can_DEPTH, "DEPTH, ft": can_DEPTH,
|
| 213 |
|
| 214 |
# Targets
|
| 215 |
"Breakout MW": tgt_bo, "BOMW": tgt_bo, "BO MW": tgt_bo,
|
| 216 |
"Breakdown MW": tgt_bd, "BDMW": tgt_bd, "BD MW": tgt_bd,
|
| 217 |
}
|
| 218 |
-
return alias
|
| 219 |
|
|
|
|
| 220 |
|
| 221 |
def _normalize_columns(df: pd.DataFrame, canonical_features: list[str], tgt_bo: str, tgt_bd: str) -> pd.DataFrame:
|
| 222 |
out = df.copy()
|
|
@@ -225,6 +223,32 @@ def _normalize_columns(df: pd.DataFrame, canonical_features: list[str], tgt_bo:
|
|
| 225 |
actual = {k: v for k, v in alias.items() if k in out.columns and k != v}
|
| 226 |
return out.rename(columns=actual)
|
| 227 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
def ensure_cols(df: pd.DataFrame, cols: list[str]) -> bool:
|
| 229 |
miss = [c for c in cols if c not in df.columns]
|
| 230 |
if miss:
|
|
@@ -607,12 +631,13 @@ try:
|
|
| 607 |
except Exception as e:
|
| 608 |
st.error(f"Failed to load models: {e}"); st.stop()
|
| 609 |
|
| 610 |
-
#
|
| 611 |
-
FEATURES
|
|
|
|
|
|
|
| 612 |
TARGET_BO = TARGET_BO_DEFAULT
|
| 613 |
TARGET_BD = TARGET_BD_DEFAULT
|
| 614 |
|
| 615 |
-
# Meta overrides
|
| 616 |
def _load_meta(p: Path):
|
| 617 |
if not p.exists(): return {}
|
| 618 |
try:
|
|
@@ -623,19 +648,33 @@ def _load_meta(p: Path):
|
|
| 623 |
meta_bo = _load_meta(BO_META_PATH)
|
| 624 |
meta_bd = _load_meta(BD_META_PATH)
|
| 625 |
|
| 626 |
-
#
|
| 627 |
-
|
| 628 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 629 |
|
| 630 |
-
|
| 631 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
| 632 |
|
| 633 |
# Session constants for easy access elsewhere
|
| 634 |
st.session_state["FEATURES"] = FEATURES
|
| 635 |
st.session_state["TARGET_BO"] = TARGET_BO
|
| 636 |
st.session_state["TARGET_BD"] = TARGET_BD
|
|
|
|
| 637 |
|
| 638 |
-
# Optional
|
| 639 |
if STRICT_VERSION_CHECK:
|
| 640 |
import numpy as _np, sklearn as _skl
|
| 641 |
msgs=[]
|
|
@@ -744,27 +783,24 @@ if st.session_state.app_step == "dev":
|
|
| 744 |
st.markdown('<div class="st-message-box st-error">Workbook must include Train/Training/training2 and Test/Testing/testing2 sheets.</div>', unsafe_allow_html=True)
|
| 745 |
st.stop()
|
| 746 |
|
| 747 |
-
# Normalize + ensure cols
|
| 748 |
-
tr =
|
| 749 |
-
te =
|
| 750 |
-
need = FEATURES + [TARGET_BO, TARGET_BD]
|
| 751 |
-
if not (ensure_cols(tr, need) and ensure_cols(te, need)):
|
| 752 |
-
st.markdown('<div class="st-message-box st-error">Missing required columns.</div>', unsafe_allow_html=True); st.stop()
|
| 753 |
|
| 754 |
# Predict with exact training feature order
|
| 755 |
-
tr[PRED_BO] = model_bo.predict(_make_X(tr, FEATURES))
|
| 756 |
-
tr[PRED_BD] = model_bd.predict(_make_X(tr, FEATURES))
|
| 757 |
-
te[PRED_BO] = model_bo.predict(_make_X(te, FEATURES))
|
| 758 |
-
te[PRED_BD] = model_bd.predict(_make_X(te, FEATURES))
|
| 759 |
|
| 760 |
st.session_state.results["Train"]=tr; st.session_state.results["Test"]=te
|
| 761 |
-
st.session_state.results["m_train_bo"]={"R": pearson_r(tr[TARGET_BO], tr[PRED_BO]), "RMSE": rmse(tr[TARGET_BO], tr[PRED_BO]), "MAE": mean_absolute_error(tr[TARGET_BO], tr[PRED_BO])}
|
| 762 |
-
st.session_state.results["m_train_bd"]={"R": pearson_r(tr[TARGET_BD], tr[PRED_BD]), "RMSE": rmse(tr[TARGET_BD], tr[PRED_BD]), "MAE": mean_absolute_error(tr[TARGET_BD], tr[PRED_BD])}
|
| 763 |
-
st.session_state.results["m_test_bo"] ={"R": pearson_r(te[TARGET_BO], te[PRED_BO]), "RMSE": rmse(te[TARGET_BO], te[PRED_BO]), "MAE": mean_absolute_error(te[TARGET_BO], te[PRED_BO])}
|
| 764 |
-
st.session_state.results["m_test_bd"] ={"R": pearson_r(te[TARGET_BD], te[PRED_BD]), "RMSE": rmse(te[TARGET_BD], te[PRED_BD]), "MAE": mean_absolute_error(te[TARGET_BD], te[PRED_BD])}
|
| 765 |
|
| 766 |
-
tr_min = tr[FEATURES].min().to_dict(); tr_max = tr[FEATURES].max().to_dict()
|
| 767 |
-
st.session_state.train_ranges = {f:(float(tr_min[f]), float(tr_max[f])) for f in FEATURES}
|
| 768 |
st.markdown('<div class="st-message-box st-success">Case has been built and results are displayed below.</div>', unsafe_allow_html=True)
|
| 769 |
|
| 770 |
def _metrics_block(lbl, m):
|
|
@@ -782,11 +818,11 @@ if st.session_state.app_step == "dev":
|
|
| 782 |
""", unsafe_allow_html=True)
|
| 783 |
t1, t2, t3 = st.tabs(["Breakout", "Breakdown", "Combined"])
|
| 784 |
with t1:
|
| 785 |
-
st.plotly_chart(track_plot_single(df, PRED_BO, actual_col=TARGET_BO, title_suffix="Breakout"), use_container_width=False, config={"displayModeBar": False, "scrollZoom": True})
|
| 786 |
-
st.pyplot(cross_plot_static(df[TARGET_BO], df[PRED_BO], xlabel=f"Actual {TARGET_BO}", ylabel=f"Predicted {TARGET_BO}", color=COLORS["pred_bo"]), use_container_width=False)
|
| 787 |
with t2:
|
| 788 |
-
st.plotly_chart(track_plot_single(df, PRED_BD, actual_col=TARGET_BD, title_suffix="Breakdown"), use_container_width=False, config={"displayModeBar": False, "scrollZoom": True})
|
| 789 |
-
st.pyplot(cross_plot_static(df[TARGET_BD], df[PRED_BD], xlabel=f"Actual {TARGET_BD}", ylabel=f"Predicted {TARGET_BD}", color=COLORS["pred_bd"]), use_container_width=False)
|
| 790 |
with t3:
|
| 791 |
st.plotly_chart(track_plot_combined(df), use_container_width=False, config={"displayModeBar": False, "scrollZoom": True})
|
| 792 |
|
|
@@ -822,29 +858,26 @@ if st.session_state.app_step == "validate":
|
|
| 822 |
if go_btn and up is not None:
|
| 823 |
book = read_book_bytes(up.getvalue())
|
| 824 |
name = find_sheet(book, ["Validation","Validate","validation2","Val","val"]) or list(book.keys())[0]
|
| 825 |
-
df =
|
| 826 |
-
need = FEATURES + [TARGET_BO, TARGET_BD]
|
| 827 |
-
if not ensure_cols(df, need):
|
| 828 |
-
st.markdown('<div class="st-message-box st-error">Missing required columns.</div>', unsafe_allow_html=True); st.stop()
|
| 829 |
|
| 830 |
-
df[PRED_BO] = model_bo.predict(_make_X(df, FEATURES))
|
| 831 |
-
df[PRED_BD] = model_bd.predict(_make_X(df, FEATURES))
|
| 832 |
st.session_state.results["Validate"]=df
|
| 833 |
|
| 834 |
ranges = st.session_state.train_ranges; oor_pct = 0.0; tbl=None
|
| 835 |
if ranges:
|
| 836 |
-
any_viol = pd.DataFrame({f:(df[f]<ranges[f][0])|(df[f]>ranges[f][1]) for f in FEATURES}).any(axis=1)
|
| 837 |
oor_pct = float(any_viol.mean()*100.0)
|
| 838 |
if any_viol.any():
|
| 839 |
-
tbl = df.loc[any_viol, FEATURES].copy()
|
| 840 |
-
for c in FEATURES:
|
| 841 |
if pd.api.types.is_numeric_dtype(tbl[c]): tbl[c] = tbl[c].round(2)
|
| 842 |
-
tbl["Violations"] = pd.DataFrame({f:(df[f]<ranges[f][0])|(df[f]>ranges[f][1]) for f in FEATURES}).loc[any_viol].apply(
|
| 843 |
lambda r:", ".join([c for c,v in r.items() if v]), axis=1
|
| 844 |
)
|
| 845 |
|
| 846 |
-
st.session_state.results["m_val_bo"]={"R": pearson_r(df[TARGET_BO], df[PRED_BO]), "RMSE": rmse(df[TARGET_BO], df[PRED_BO]), "MAE": mean_absolute_error(df[TARGET_BO], df[PRED_BO])}
|
| 847 |
-
st.session_state.results["m_val_bd"]={"R": pearson_r(df[TARGET_BD], df[PRED_BD]), "RMSE": rmse(df[TARGET_BD], df[PRED_BD]), "MAE": mean_absolute_error(df[TARGET_BD], df[PRED_BD])}
|
| 848 |
st.session_state.results["sv_val"]={"n":len(df), "bo_min":float(df[PRED_BO].min()), "bo_max":float(df[PRED_BO].max()),
|
| 849 |
"bd_min":float(df[PRED_BD].min()), "bd_max":float(df[PRED_BD].max()), "oor":oor_pct}
|
| 850 |
st.session_state.results["oor_tbl"]=tbl
|
|
@@ -860,14 +893,14 @@ if st.session_state.app_step == "validate":
|
|
| 860 |
|
| 861 |
t1, t2, t3 = st.tabs(["Breakout", "Breakdown", "Combined"])
|
| 862 |
with t1:
|
| 863 |
-
st.plotly_chart(track_plot_single(df, PRED_BO, actual_col=TARGET_BO, title_suffix="Breakout"),
|
| 864 |
use_container_width=False, config={"displayModeBar": False, "scrollZoom": True})
|
| 865 |
-
st.pyplot(cross_plot_static(df[TARGET_BO], df[PRED_BO], f"Actual {TARGET_BO}", f"Predicted {TARGET_BO}", COLORS["pred_bo"]),
|
| 866 |
use_container_width=False)
|
| 867 |
with t2:
|
| 868 |
-
st.plotly_chart(track_plot_single(df, PRED_BD, actual_col=TARGET_BD, title_suffix="Breakdown"),
|
| 869 |
use_container_width=False, config={"displayModeBar": False, "scrollZoom": True})
|
| 870 |
-
st.pyplot(cross_plot_static(df[TARGET_BD], df[PRED_BD], f"Actual {TARGET_BD}", f"Predicted {TARGET_BD}", COLORS["pred_bd"]),
|
| 871 |
use_container_width=False)
|
| 872 |
with t3:
|
| 873 |
st.plotly_chart(track_plot_combined(df), use_container_width=False, config={"displayModeBar": False, "scrollZoom": True})
|
|
@@ -899,16 +932,15 @@ if st.session_state.app_step == "predict":
|
|
| 899 |
|
| 900 |
if go_btn and up is not None:
|
| 901 |
book = read_book_bytes(up.getvalue()); name = list(book.keys())[0]
|
| 902 |
-
df =
|
| 903 |
-
|
| 904 |
-
|
| 905 |
-
df[
|
| 906 |
-
df[PRED_BD] = model_bd.predict(_make_X(df, FEATURES))
|
| 907 |
st.session_state.results["PredictOnly"]=df
|
| 908 |
|
| 909 |
ranges = st.session_state.train_ranges; oor_pct = 0.0
|
| 910 |
if ranges:
|
| 911 |
-
any_viol = pd.DataFrame({f:(df[f]<ranges[f][0])|(df[f]>ranges[f][1]) for f in FEATURES}).any(axis=1)
|
| 912 |
oor_pct = float(any_viol.mean()*100.0)
|
| 913 |
st.session_state.results["sv_pred"]={
|
| 914 |
"n":len(df),
|
|
@@ -962,12 +994,12 @@ if st.session_state.show_preview_modal:
|
|
| 962 |
tabs = st.tabs(names)
|
| 963 |
for t, name in zip(tabs, names):
|
| 964 |
with t:
|
| 965 |
-
df =
|
| 966 |
t1, t2 = st.tabs(["Tracks", "Summary"])
|
| 967 |
with t1:
|
| 968 |
-
st.pyplot(preview_tracks(df, FEATURES), use_container_width=True)
|
| 969 |
with t2:
|
| 970 |
-
feat_present = [c for c in FEATURES if c in df.columns]
|
| 971 |
if not feat_present:
|
| 972 |
st.info("No feature columns found to summarize.")
|
| 973 |
else:
|
|
|
|
| 1 |
# app.py — ST_GeoMech_SMW
|
|
|
|
| 2 |
import io, json, os, base64, math
|
| 3 |
from pathlib import Path
|
| 4 |
import streamlit as st
|
|
|
|
| 22 |
APP_NAME = "ST_GeoMech_SMW"
|
| 23 |
TAGLINE = "Real-Time Upper/Lower Mud Weight (MW) Limits For Safe Drilling"
|
| 24 |
|
| 25 |
+
# Defaults (can be overridden by metas or model.feature_names_in_)
|
| 26 |
FEATURES_DEFAULT = [
|
| 27 |
"WOB (klbf)",
|
| 28 |
"Torque (kft.lbf)",
|
|
|
|
| 44 |
BO_META_PATH = MODELS_DIR / "bo_meta.json"
|
| 45 |
BD_META_PATH = MODELS_DIR / "bd_meta.json"
|
| 46 |
|
|
|
|
| 47 |
COLORS = {
|
| 48 |
"pred_bo": "#1f77b4", # blue
|
| 49 |
"pred_bd": "#d62728", # red
|
|
|
|
| 195 |
can_ROP = pick(canonical_features, ["ROP (ft/h)", "ROP(ft/h)"])
|
| 196 |
can_FR = pick(canonical_features, ["Flow Rate (gpm)", "Flow Rate, gpm", "Flow Rate,gpm", "Flow Rate , gpm"])
|
| 197 |
|
| 198 |
+
# Depth canonical = whatever is in training FEATURES (Depth or Depth (ft), etc.)
|
| 199 |
can_DEPTH = pick(canonical_features, ["Depth", "Depth (ft)", "Depth, ft", "Depth(ft)", "DEPTH, ft"])
|
| 200 |
|
| 201 |
+
return {
|
| 202 |
"WOB (klbf)": can_WOB, "WOB, klbf": can_WOB, "WOB(klbf)": can_WOB, "WOB( klbf)": can_WOB,
|
| 203 |
"Torque (kft.lbf)": can_TORQUE, "Torque(kft.lbf)": can_TORQUE, "TORQUE(kft.lbf)": can_TORQUE,
|
| 204 |
"SPP (psi)": can_SPP, "SPP(psi)": can_SPP,
|
|
|
|
| 206 |
"ROP (ft/h)": can_ROP, "ROP(ft/h)": can_ROP,
|
| 207 |
"Flow Rate (gpm)": can_FR, "Flow Rate, gpm": can_FR, "Flow Rate,gpm": can_FR, "Flow Rate , gpm": can_FR,
|
| 208 |
|
| 209 |
+
# Depth aliases
|
| 210 |
"Depth": can_DEPTH, "Depth (ft)": can_DEPTH, "Depth, ft": can_DEPTH, "Depth(ft)": can_DEPTH, "DEPTH, ft": can_DEPTH,
|
| 211 |
|
| 212 |
# Targets
|
| 213 |
"Breakout MW": tgt_bo, "BOMW": tgt_bo, "BO MW": tgt_bo,
|
| 214 |
"Breakdown MW": tgt_bd, "BDMW": tgt_bd, "BD MW": tgt_bd,
|
| 215 |
}
|
|
|
|
| 216 |
|
| 217 |
+
DEPTH_CANDIDATES = ["Depth", "Depth (ft)", "Depth, ft", "Depth(ft)", "DEPTH, ft"]
|
| 218 |
|
| 219 |
def _normalize_columns(df: pd.DataFrame, canonical_features: list[str], tgt_bo: str, tgt_bd: str) -> pd.DataFrame:
|
| 220 |
out = df.copy()
|
|
|
|
| 223 |
actual = {k: v for k, v in alias.items() if k in out.columns and k != v}
|
| 224 |
return out.rename(columns=actual)
|
| 225 |
|
| 226 |
+
def _coerce_depth(df: pd.DataFrame, canon_depth: str | None) -> pd.DataFrame:
|
| 227 |
+
"""If the model expects a Depth-like column, rename any variant to it."""
|
| 228 |
+
if not canon_depth:
|
| 229 |
+
return df
|
| 230 |
+
if canon_depth in df.columns:
|
| 231 |
+
return df
|
| 232 |
+
for c in DEPTH_CANDIDATES:
|
| 233 |
+
if c in df.columns:
|
| 234 |
+
return df.rename(columns={c: canon_depth})
|
| 235 |
+
return df
|
| 236 |
+
|
| 237 |
+
def _prepare_table(raw_df: pd.DataFrame, stage: str, features: list[str],
|
| 238 |
+
tgt_bo: str, tgt_bd: str, canon_depth: str | None) -> pd.DataFrame:
|
| 239 |
+
"""Normalize headers, coerce depth name, and validate required columns."""
|
| 240 |
+
df = _normalize_columns(raw_df, features, tgt_bo, tgt_bd)
|
| 241 |
+
df = _coerce_depth(df, canon_depth)
|
| 242 |
+
missing = [c for c in features if c not in df.columns]
|
| 243 |
+
if missing:
|
| 244 |
+
st.error(
|
| 245 |
+
f"{stage}: Missing required column(s): {missing}\n\n"
|
| 246 |
+
f"Found columns: {list(df.columns)}\n\n"
|
| 247 |
+
f"Expected features: {features}"
|
| 248 |
+
)
|
| 249 |
+
st.stop()
|
| 250 |
+
return df
|
| 251 |
+
|
| 252 |
def ensure_cols(df: pd.DataFrame, cols: list[str]) -> bool:
|
| 253 |
miss = [c for c in cols if c not in df.columns]
|
| 254 |
if miss:
|
|
|
|
| 631 |
except Exception as e:
|
| 632 |
st.error(f"Failed to load models: {e}"); st.stop()
|
| 633 |
|
| 634 |
+
# ---------------------------
|
| 635 |
+
# Resolve FEATURES & targets
|
| 636 |
+
# ---------------------------
|
| 637 |
+
FEATURES = FEATURES_DEFAULT[:] # fallback
|
| 638 |
TARGET_BO = TARGET_BO_DEFAULT
|
| 639 |
TARGET_BD = TARGET_BD_DEFAULT
|
| 640 |
|
|
|
|
| 641 |
def _load_meta(p: Path):
|
| 642 |
if not p.exists(): return {}
|
| 643 |
try:
|
|
|
|
| 648 |
meta_bo = _load_meta(BO_META_PATH)
|
| 649 |
meta_bd = _load_meta(BD_META_PATH)
|
| 650 |
|
| 651 |
+
# Prefer the model's trained feature names (golden source)
|
| 652 |
+
try:
|
| 653 |
+
if hasattr(model_bo, "feature_names_in_"):
|
| 654 |
+
FEATURES = [str(x) for x in model_bo.feature_names_in_]
|
| 655 |
+
except Exception:
|
| 656 |
+
pass
|
| 657 |
+
|
| 658 |
+
# If still not set, fall back to BO meta, then BD meta, then defaults
|
| 659 |
+
if not FEATURES and meta_bo.get("features"):
|
| 660 |
+
FEATURES = [str(x) for x in meta_bo["features"]]
|
| 661 |
+
elif not FEATURES and meta_bd.get("features"):
|
| 662 |
+
FEATURES = [str(x) for x in meta_bd["features"]]
|
| 663 |
|
| 664 |
+
# Targets (from metas if provided)
|
| 665 |
+
if meta_bo.get("target"): TARGET_BO = str(meta_bo["target"])
|
| 666 |
+
if meta_bd.get("target"): TARGET_BD = str(meta_bd["target"])
|
| 667 |
+
|
| 668 |
+
# Canonical Depth name the model expects (if any)
|
| 669 |
+
CANON_DEPTH = next((c for c in FEATURES if str(c).strip().lower().startswith("depth")), None)
|
| 670 |
|
| 671 |
# Session constants for easy access elsewhere
|
| 672 |
st.session_state["FEATURES"] = FEATURES
|
| 673 |
st.session_state["TARGET_BO"] = TARGET_BO
|
| 674 |
st.session_state["TARGET_BD"] = TARGET_BD
|
| 675 |
+
st.session_state["CANON_DEPTH"] = CANON_DEPTH
|
| 676 |
|
| 677 |
+
# Optional strict version banner
|
| 678 |
if STRICT_VERSION_CHECK:
|
| 679 |
import numpy as _np, sklearn as _skl
|
| 680 |
msgs=[]
|
|
|
|
| 783 |
st.markdown('<div class="st-message-box st-error">Workbook must include Train/Training/training2 and Test/Testing/testing2 sheets.</div>', unsafe_allow_html=True)
|
| 784 |
st.stop()
|
| 785 |
|
| 786 |
+
# Normalize + ensure cols (robust depth handling)
|
| 787 |
+
tr = _prepare_table(book[sh_train].copy(), "Training", st.session_state["FEATURES"], st.session_state["TARGET_BO"], st.session_state["TARGET_BD"], st.session_state["CANON_DEPTH"])
|
| 788 |
+
te = _prepare_table(book[sh_test].copy(), "Testing", st.session_state["FEATURES"], st.session_state["TARGET_BO"], st.session_state["TARGET_BD"], st.session_state["CANON_DEPTH"])
|
|
|
|
|
|
|
|
|
|
| 789 |
|
| 790 |
# Predict with exact training feature order
|
| 791 |
+
tr[PRED_BO] = model_bo.predict(_make_X(tr, st.session_state["FEATURES"]))
|
| 792 |
+
tr[PRED_BD] = model_bd.predict(_make_X(tr, st.session_state["FEATURES"]))
|
| 793 |
+
te[PRED_BO] = model_bo.predict(_make_X(te, st.session_state["FEATURES"]))
|
| 794 |
+
te[PRED_BD] = model_bd.predict(_make_X(te, st.session_state["FEATURES"]))
|
| 795 |
|
| 796 |
st.session_state.results["Train"]=tr; st.session_state.results["Test"]=te
|
| 797 |
+
st.session_state.results["m_train_bo"]={"R": pearson_r(tr[st.session_state["TARGET_BO"]], tr[PRED_BO]), "RMSE": rmse(tr[st.session_state["TARGET_BO"]], tr[PRED_BO]), "MAE": mean_absolute_error(tr[st.session_state["TARGET_BO"]], tr[PRED_BO])}
|
| 798 |
+
st.session_state.results["m_train_bd"]={"R": pearson_r(tr[st.session_state["TARGET_BD"]], tr[PRED_BD]), "RMSE": rmse(tr[st.session_state["TARGET_BD"]], tr[PRED_BD]), "MAE": mean_absolute_error(tr[st.session_state["TARGET_BD"]], tr[PRED_BD])}
|
| 799 |
+
st.session_state.results["m_test_bo"] ={"R": pearson_r(te[st.session_state["TARGET_BO"]], te[PRED_BO]), "RMSE": rmse(te[st.session_state["TARGET_BO"]], te[PRED_BO]), "MAE": mean_absolute_error(te[st.session_state["TARGET_BO"]], te[PRED_BO])}
|
| 800 |
+
st.session_state.results["m_test_bd"] ={"R": pearson_r(te[st.session_state["TARGET_BD"]], te[PRED_BD]), "RMSE": rmse(te[st.session_state["TARGET_BD"]], te[PRED_BD]), "MAE": mean_absolute_error(te[st.session_state["TARGET_BD"]], te[PRED_BD])}
|
| 801 |
|
| 802 |
+
tr_min = tr[st.session_state["FEATURES"]].min().to_dict(); tr_max = tr[st.session_state["FEATURES"]].max().to_dict()
|
| 803 |
+
st.session_state.train_ranges = {f:(float(tr_min[f]), float(tr_max[f])) for f in st.session_state["FEATURES"]}
|
| 804 |
st.markdown('<div class="st-message-box st-success">Case has been built and results are displayed below.</div>', unsafe_allow_html=True)
|
| 805 |
|
| 806 |
def _metrics_block(lbl, m):
|
|
|
|
| 818 |
""", unsafe_allow_html=True)
|
| 819 |
t1, t2, t3 = st.tabs(["Breakout", "Breakdown", "Combined"])
|
| 820 |
with t1:
|
| 821 |
+
st.plotly_chart(track_plot_single(df, PRED_BO, actual_col=st.session_state["TARGET_BO"], title_suffix="Breakout"), use_container_width=False, config={"displayModeBar": False, "scrollZoom": True})
|
| 822 |
+
st.pyplot(cross_plot_static(df[st.session_state["TARGET_BO"]], df[PRED_BO], xlabel=f"Actual {st.session_state['TARGET_BO']}", ylabel=f"Predicted {st.session_state['TARGET_BO']}", color=COLORS["pred_bo"]), use_container_width=False)
|
| 823 |
with t2:
|
| 824 |
+
st.plotly_chart(track_plot_single(df, PRED_BD, actual_col=st.session_state["TARGET_BD"], title_suffix="Breakdown"), use_container_width=False, config={"displayModeBar": False, "scrollZoom": True})
|
| 825 |
+
st.pyplot(cross_plot_static(df[st.session_state["TARGET_BD"]], df[PRED_BD], xlabel=f"Actual {st.session_state['TARGET_BD']}", ylabel=f"Predicted {st.session_state['TARGET_BD']}", color=COLORS["pred_bd"]), use_container_width=False)
|
| 826 |
with t3:
|
| 827 |
st.plotly_chart(track_plot_combined(df), use_container_width=False, config={"displayModeBar": False, "scrollZoom": True})
|
| 828 |
|
|
|
|
| 858 |
if go_btn and up is not None:
|
| 859 |
book = read_book_bytes(up.getvalue())
|
| 860 |
name = find_sheet(book, ["Validation","Validate","validation2","Val","val"]) or list(book.keys())[0]
|
| 861 |
+
df = _prepare_table(book[name].copy(), "Validation", st.session_state["FEATURES"], st.session_state["TARGET_BO"], st.session_state["TARGET_BD"], st.session_state["CANON_DEPTH"])
|
|
|
|
|
|
|
|
|
|
| 862 |
|
| 863 |
+
df[PRED_BO] = model_bo.predict(_make_X(df, st.session_state["FEATURES"]))
|
| 864 |
+
df[PRED_BD] = model_bd.predict(_make_X(df, st.session_state["FEATURES"]))
|
| 865 |
st.session_state.results["Validate"]=df
|
| 866 |
|
| 867 |
ranges = st.session_state.train_ranges; oor_pct = 0.0; tbl=None
|
| 868 |
if ranges:
|
| 869 |
+
any_viol = pd.DataFrame({f:(df[f]<ranges[f][0])|(df[f]>ranges[f][1]) for f in st.session_state["FEATURES"]}).any(axis=1)
|
| 870 |
oor_pct = float(any_viol.mean()*100.0)
|
| 871 |
if any_viol.any():
|
| 872 |
+
tbl = df.loc[any_viol, st.session_state["FEATURES"]].copy()
|
| 873 |
+
for c in st.session_state["FEATURES"]:
|
| 874 |
if pd.api.types.is_numeric_dtype(tbl[c]): tbl[c] = tbl[c].round(2)
|
| 875 |
+
tbl["Violations"] = pd.DataFrame({f:(df[f]<ranges[f][0])|(df[f]>ranges[f][1]) for f in st.session_state["FEATURES"]}).loc[any_viol].apply(
|
| 876 |
lambda r:", ".join([c for c,v in r.items() if v]), axis=1
|
| 877 |
)
|
| 878 |
|
| 879 |
+
st.session_state.results["m_val_bo"]={"R": pearson_r(df[st.session_state["TARGET_BO"]], df[PRED_BO]), "RMSE": rmse(df[st.session_state["TARGET_BO"]], df[PRED_BO]), "MAE": mean_absolute_error(df[st.session_state["TARGET_BO"]], df[PRED_BO])}
|
| 880 |
+
st.session_state.results["m_val_bd"]={"R": pearson_r(df[st.session_state["TARGET_BD"]], df[PRED_BD]), "RMSE": rmse(df[st.session_state["TARGET_BD"]], df[PRED_BD]), "MAE": mean_absolute_error(df[st.session_state["TARGET_BD"]], df[PRED_BD])}
|
| 881 |
st.session_state.results["sv_val"]={"n":len(df), "bo_min":float(df[PRED_BO].min()), "bo_max":float(df[PRED_BO].max()),
|
| 882 |
"bd_min":float(df[PRED_BD].min()), "bd_max":float(df[PRED_BD].max()), "oor":oor_pct}
|
| 883 |
st.session_state.results["oor_tbl"]=tbl
|
|
|
|
| 893 |
|
| 894 |
t1, t2, t3 = st.tabs(["Breakout", "Breakdown", "Combined"])
|
| 895 |
with t1:
|
| 896 |
+
st.plotly_chart(track_plot_single(df, PRED_BO, actual_col=st.session_state["TARGET_BO"], title_suffix="Breakout"),
|
| 897 |
use_container_width=False, config={"displayModeBar": False, "scrollZoom": True})
|
| 898 |
+
st.pyplot(cross_plot_static(df[st.session_state["TARGET_BO"]], df[PRED_BO], f"Actual {st.session_state['TARGET_BO']}", f"Predicted {st.session_state['TARGET_BO']}", COLORS["pred_bo"]),
|
| 899 |
use_container_width=False)
|
| 900 |
with t2:
|
| 901 |
+
st.plotly_chart(track_plot_single(df, PRED_BD, actual_col=st.session_state["TARGET_BD"], title_suffix="Breakdown"),
|
| 902 |
use_container_width=False, config={"displayModeBar": False, "scrollZoom": True})
|
| 903 |
+
st.pyplot(cross_plot_static(df[st.session_state["TARGET_BD"]], df[PRED_BD], f"Actual {st.session_state['TARGET_BD']}", f"Predicted {st.session_state['TARGET_BD']}", COLORS["pred_bd"]),
|
| 904 |
use_container_width=False)
|
| 905 |
with t3:
|
| 906 |
st.plotly_chart(track_plot_combined(df), use_container_width=False, config={"displayModeBar": False, "scrollZoom": True})
|
|
|
|
| 932 |
|
| 933 |
if go_btn and up is not None:
|
| 934 |
book = read_book_bytes(up.getvalue()); name = list(book.keys())[0]
|
| 935 |
+
df = _prepare_table(book[name].copy(), "Prediction", st.session_state["FEATURES"], st.session_state["TARGET_BO"], st.session_state["TARGET_BD"], st.session_state["CANON_DEPTH"])
|
| 936 |
+
|
| 937 |
+
df[PRED_BO] = model_bo.predict(_make_X(df, st.session_state["FEATURES"]))
|
| 938 |
+
df[PRED_BD] = model_bd.predict(_make_X(df, st.session_state["FEATURES"]))
|
|
|
|
| 939 |
st.session_state.results["PredictOnly"]=df
|
| 940 |
|
| 941 |
ranges = st.session_state.train_ranges; oor_pct = 0.0
|
| 942 |
if ranges:
|
| 943 |
+
any_viol = pd.DataFrame({f:(df[f]<ranges[f][0])|(df[f]>ranges[f][1]) for f in st.session_state["FEATURES"]}).any(axis=1)
|
| 944 |
oor_pct = float(any_viol.mean()*100.0)
|
| 945 |
st.session_state.results["sv_pred"]={
|
| 946 |
"n":len(df),
|
|
|
|
| 994 |
tabs = st.tabs(names)
|
| 995 |
for t, name in zip(tabs, names):
|
| 996 |
with t:
|
| 997 |
+
df = _prepare_table(book_to_preview[name], "Preview", st.session_state["FEATURES"], st.session_state["TARGET_BO"], st.session_state["TARGET_BD"], st.session_state["CANON_DEPTH"])
|
| 998 |
t1, t2 = st.tabs(["Tracks", "Summary"])
|
| 999 |
with t1:
|
| 1000 |
+
st.pyplot(preview_tracks(df, st.session_state["FEATURES"]), use_container_width=True)
|
| 1001 |
with t2:
|
| 1002 |
+
feat_present = [c for c in st.session_state["FEATURES"] if c in df.columns]
|
| 1003 |
if not feat_present:
|
| 1004 |
st.info("No feature columns found to summarize.")
|
| 1005 |
else:
|