UCS2014 commited on
Commit
b69b6e7
·
verified ·
1 Parent(s): 5cca3cc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +68 -64
app.py CHANGED
@@ -5,12 +5,12 @@ import pandas as pd
5
  import numpy as np
6
  import joblib
7
  import matplotlib
8
- matplotlib.use("Agg") # safe non-GUI backend for cloud
9
  import matplotlib.pyplot as plt
10
  from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error
11
 
12
  # =========================
13
- # Defaults (overridden by models/meta.json or model.feature_names_in_)
14
  # =========================
15
  FEATURES = ["Q, gpm", "SPP(psi)", "T (kft.lbf)", "WOB (klbf)", "ROP (ft/h)"]
16
  TARGET = "UCS"
@@ -18,12 +18,7 @@ MODELS_DIR = Path("models")
18
  DEFAULT_MODEL = MODELS_DIR / "ucs_rf.joblib"
19
  MODEL_FALLBACKS = [MODELS_DIR / "model.joblib", MODELS_DIR / "model.pkl"]
20
 
21
- # Colors for plots
22
- COLORS = {
23
- "pred": "#1f77b4", # blue (predicted)
24
- "actual": "#f2b702", # yellow (actual)
25
- "ref": "#5a5a5a" # grey 1:1 line
26
- }
27
 
28
  # =========================
29
  # Page / Theme
@@ -39,7 +34,7 @@ st.markdown(
39
  .stButton>button{ background:#007bff; color:#fff; font-weight:bold; border-radius:8px; border:none; padding:10px 24px; }
40
  .stButton>button:hover{ background:#0056b3; }
41
  .st-hero { display:flex; align-items:center; gap:16px; padding-top: 4px; }
42
- .st-hero .brand { width:110px; height:110px; object-fit:contain; } /* bigger logo */
43
  .st-hero h1 { margin:0; line-height:1.05; }
44
  .st-hero .tagline { margin:2px 0 0 2px; color:#6b7280; font-size:1.05rem; font-style:italic; }
45
  [data-testid="stBlock"]{ margin-top:0 !important; }
@@ -54,7 +49,7 @@ st.markdown(
54
  try:
55
  dialog = st.dialog
56
  except AttributeError:
57
- # Back-compat shim if st.dialog isn't available
58
  def dialog(title):
59
  def deco(fn):
60
  def wrapper(*args, **kwargs):
@@ -64,7 +59,6 @@ except AttributeError:
64
  return deco
65
 
66
  def _get_model_url():
67
- """Read optional MODEL_URL from environment only (avoid st.secrets banner)."""
68
  return (os.environ.get("MODEL_URL", "") or "").strip()
69
 
70
  def rmse(y_true, y_pred): return float(np.sqrt(mean_squared_error(y_true, y_pred)))
@@ -176,8 +170,7 @@ def make_index_tracks(df: pd.DataFrame, cols: list[str]):
176
  if n == 0:
177
  fig, ax = plt.subplots(figsize=(4, 2))
178
  ax.text(0.5, 0.5, "No selected columns in sheet", ha="center", va="center")
179
- ax.axis("off")
180
- return fig
181
  width_per = 2.2
182
  fig_h = 7.0
183
  fig, axes = plt.subplots(1, n, figsize=(width_per * n, fig_h), sharey=True, dpi=100)
@@ -230,7 +223,7 @@ def preview_modal_val(book: dict[str, pd.DataFrame], feature_cols: list[str]):
230
  with t2: st.dataframe(stats_table(df, feature_cols), use_container_width=True)
231
 
232
  # =========================
233
- # Model presence (local or optional download)
234
  # =========================
235
  MODEL_URL = _get_model_url()
236
 
@@ -265,7 +258,7 @@ except Exception as e:
265
  st.error(f"Failed to load model: {model_path}\n{e}")
266
  st.stop()
267
 
268
- # Meta overrides or inference
269
  meta_path = MODELS_DIR / "meta.json"
270
  if meta_path.exists():
271
  try:
@@ -285,12 +278,14 @@ if "train_ranges" not in st.session_state: st.session_state.train_ranges = None
285
  if "dev_ready" not in st.session_state: st.session_state.dev_ready = False
286
  if "dev_file_loaded" not in st.session_state: st.session_state.dev_file_loaded = False
287
  if "dev_previewed" not in st.session_state: st.session_state.dev_previewed = False
 
 
288
 
289
  if ("Train" in st.session_state.results) or ("Test" in st.session_state.results):
290
  st.session_state.dev_ready = True
291
 
292
  # =========================
293
- # Hero header (logo + title)
294
  # =========================
295
  st.markdown(
296
  f"""
@@ -332,70 +327,83 @@ if st.session_state.app_step == "intro":
332
  st.session_state.app_step = "dev"; st.rerun()
333
 
334
  # =========================
335
- # MODEL DEVELOPMENT (Train/Test)
336
  # =========================
337
  if st.session_state.app_step == "dev":
338
  st.sidebar.header("Model Development Data")
339
-
340
  dev_label = "Upload Data (Excel)" if not st.session_state.get("dev_file_name") else "Replace data (Excel)"
341
  train_test_file = st.sidebar.file_uploader(dev_label, type=["xlsx","xls"], key="dev_upload")
342
 
343
- # If a file is uploaded, parse once and set flags
344
  if train_test_file is not None:
345
- st.session_state.dev_file_name = train_test_file.name
346
- _book_tmp = read_book(train_test_file)
347
- if _book_tmp:
348
- first_df = next(iter(_book_tmp.values()))
349
- st.sidebar.caption(f"**Data loaded:** {train_test_file.name} • {first_df.shape[0]} rows × {first_df.shape[1]} cols")
 
 
 
 
 
 
 
 
 
 
 
 
 
350
  st.session_state.dev_file_loaded = True
351
- # Reset preview + readiness on new upload
352
- st.session_state.dev_previewed = False
353
- st.session_state.dev_ready = False
354
 
355
- # Preview button (modal)
356
  preview_btn = st.sidebar.button("Preview data", use_container_width=True, disabled=not st.session_state.dev_file_loaded)
357
  if preview_btn and st.session_state.dev_file_loaded and train_test_file is not None:
358
- _book = read_book(train_test_file)
359
- st.session_state.dev_previewed = True # mark previewed
360
- preview_modal_dev(_book, FEATURES)
361
 
362
  run_btn = st.sidebar.button("Run Model", type="primary", use_container_width=True)
363
 
364
- # Proceed button BELOW run, visible always; enables after first successful run
365
- st.sidebar.button(
366
  "Proceed to Prediction ▶",
367
  use_container_width=True,
368
- disabled=not st.session_state.dev_ready,
369
- on_click=(lambda: st.session_state.update(app_step="predict")) if st.session_state.dev_ready else None,
370
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
 
372
- # ----- Title & helper (ALWAYS visible; state-aware) -----
373
- st.subheader("Model Development")
374
- if st.session_state.dev_ready:
375
- st.success("Case has been built and results are displayed below.")
376
- elif st.session_state.dev_file_loaded and st.session_state.dev_previewed:
377
- st.info("Previewed ✓ — now click **Run Model** to build the case.")
378
- elif st.session_state.dev_file_loaded:
379
- st.info("📄 **Preview uploaded data** using the sidebar button, then click **Run Model**.")
380
- else:
381
- st.write("**Upload your data to build a case, then run the model to review development performance.**")
382
-
383
  if run_btn and train_test_file is not None:
384
  with st.status("Processing…", expanded=False) as status:
385
  book = read_book(train_test_file)
386
  if not book: status.update(label="Failed to read workbook.", state="error"); st.stop()
387
  status.update(label="Workbook read ✓")
388
-
389
- # Internally still expect Train/Test sheets
390
  sh_train = find_sheet(book, ["Train","Training","training2","train","training"])
391
  sh_test = find_sheet(book, ["Test","Testing","testing2","test","testing"])
392
  if sh_train is None or sh_test is None:
393
  status.update(label="Workbook must include Train/Training/training2 and Test/Testing/testing2.", state="error"); st.stop()
394
-
395
  df_tr = book[sh_train].copy(); df_te = book[sh_test].copy()
396
  if not (ensure_cols(df_tr, FEATURES + [TARGET]) and ensure_cols(df_te, FEATURES + [TARGET])):
397
  status.update(label="Missing required columns.", state="error"); st.stop()
398
-
399
  status.update(label="Columns validated ✓"); status.update(label="Predicting…")
400
 
401
  df_tr["UCS_Pred"] = model.predict(df_tr[FEATURES])
@@ -416,11 +424,11 @@ if st.session_state.app_step == "dev":
416
  tr_min = df_tr[FEATURES].min().to_dict(); tr_max = df_tr[FEATURES].max().to_dict()
417
  st.session_state.train_ranges = {f:(float(tr_min[f]), float(tr_max[f])) for f in FEATURES}
418
 
419
- st.session_state.dev_ready = True # enable Proceed button immediately
420
  status.update(label="Done ✓", state="complete"); toast("Model run complete 🚀")
421
- st.rerun() # refresh UI to reflect new helper & enabled button
422
 
423
- # Results
424
  if ("Train" in st.session_state.results) or ("Test" in st.session_state.results):
425
  tab1, tab2 = st.tabs(["Training", "Testing"])
426
  if "Train" in st.session_state.results:
@@ -469,7 +477,6 @@ if st.session_state.app_step == "predict":
469
  val_label = "Upload Validation Excel" if not st.session_state.get("val_file_name") else "Replace data (Excel)"
470
  validation_file = st.sidebar.file_uploader(val_label, type=["xlsx","xls"], key="val_upload")
471
  if validation_file is not None:
472
- st.session_state.val_file_name = validation_file.name
473
  _book_tmp = read_book(validation_file)
474
  if _book_tmp:
475
  first_df = next(iter(_book_tmp.values()))
@@ -545,14 +552,10 @@ if st.session_state.app_step == "predict":
545
  else:
546
  st.info("Actual UCS values are not available in the validation data. Cross-plot cannot be generated.")
547
  with right:
548
- st.pyplot(
549
- depth_or_index_track(
550
- st.session_state.results["Validate"],
551
- title=None,
552
- include_actual=(TARGET in st.session_state.results["Validate"].columns)
553
- ),
554
- use_container_width=True
555
- )
556
 
557
  if oor_table is not None:
558
  st.write("*Out-of-range rows (vs. Training min–max):*")
@@ -574,13 +577,14 @@ if st.session_state.app_step == "predict":
574
  st.warning(str(e))
575
 
576
  # =========================
577
- # Footer (with website link)
578
  # =========================
579
  st.markdown("---")
580
  st.markdown(
581
  "<div style='text-align:center; color:#6b7280;'>"
582
  "ST_GeoMech_UCS • © Smart Thinking • "
583
- "<a href='https://www.smartthinking.com.sa' target='_blank'>smartthinking.com.sa</a>"
 
584
  "</div>",
585
  unsafe_allow_html=True
586
  )
 
5
  import numpy as np
6
  import joblib
7
  import matplotlib
8
+ matplotlib.use("Agg")
9
  import matplotlib.pyplot as plt
10
  from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error
11
 
12
  # =========================
13
+ # Defaults
14
  # =========================
15
  FEATURES = ["Q, gpm", "SPP(psi)", "T (kft.lbf)", "WOB (klbf)", "ROP (ft/h)"]
16
  TARGET = "UCS"
 
18
  DEFAULT_MODEL = MODELS_DIR / "ucs_rf.joblib"
19
  MODEL_FALLBACKS = [MODELS_DIR / "model.joblib", MODELS_DIR / "model.pkl"]
20
 
21
+ COLORS = {"pred": "#1f77b4", "actual": "#f2b702", "ref": "#5a5a5a"}
 
 
 
 
 
22
 
23
  # =========================
24
  # Page / Theme
 
34
  .stButton>button{ background:#007bff; color:#fff; font-weight:bold; border-radius:8px; border:none; padding:10px 24px; }
35
  .stButton>button:hover{ background:#0056b3; }
36
  .st-hero { display:flex; align-items:center; gap:16px; padding-top: 4px; }
37
+ .st-hero .brand { width:110px; height:110px; object-fit:contain; }
38
  .st-hero h1 { margin:0; line-height:1.05; }
39
  .st-hero .tagline { margin:2px 0 0 2px; color:#6b7280; font-size:1.05rem; font-style:italic; }
40
  [data-testid="stBlock"]{ margin-top:0 !important; }
 
49
  try:
50
  dialog = st.dialog
51
  except AttributeError:
52
+ # Fallback (expander) if st.dialog is unavailable
53
  def dialog(title):
54
  def deco(fn):
55
  def wrapper(*args, **kwargs):
 
59
  return deco
60
 
61
  def _get_model_url():
 
62
  return (os.environ.get("MODEL_URL", "") or "").strip()
63
 
64
  def rmse(y_true, y_pred): return float(np.sqrt(mean_squared_error(y_true, y_pred)))
 
170
  if n == 0:
171
  fig, ax = plt.subplots(figsize=(4, 2))
172
  ax.text(0.5, 0.5, "No selected columns in sheet", ha="center", va="center")
173
+ ax.axis("off"); return fig
 
174
  width_per = 2.2
175
  fig_h = 7.0
176
  fig, axes = plt.subplots(1, n, figsize=(width_per * n, fig_h), sharey=True, dpi=100)
 
223
  with t2: st.dataframe(stats_table(df, feature_cols), use_container_width=True)
224
 
225
  # =========================
226
+ # Model presence
227
  # =========================
228
  MODEL_URL = _get_model_url()
229
 
 
258
  st.error(f"Failed to load model: {model_path}\n{e}")
259
  st.stop()
260
 
261
+ # Meta overrides / inference
262
  meta_path = MODELS_DIR / "meta.json"
263
  if meta_path.exists():
264
  try:
 
278
  if "dev_ready" not in st.session_state: st.session_state.dev_ready = False
279
  if "dev_file_loaded" not in st.session_state: st.session_state.dev_file_loaded = False
280
  if "dev_previewed" not in st.session_state: st.session_state.dev_previewed = False
281
+ if "dev_file_signature" not in st.session_state: st.session_state.dev_file_signature = None
282
+ if "dev_preview_request" not in st.session_state: st.session_state.dev_preview_request = False
283
 
284
  if ("Train" in st.session_state.results) or ("Test" in st.session_state.results):
285
  st.session_state.dev_ready = True
286
 
287
  # =========================
288
+ # Hero header
289
  # =========================
290
  st.markdown(
291
  f"""
 
327
  st.session_state.app_step = "dev"; st.rerun()
328
 
329
  # =========================
330
+ # MODEL DEVELOPMENT
331
  # =========================
332
  if st.session_state.app_step == "dev":
333
  st.sidebar.header("Model Development Data")
 
334
  dev_label = "Upload Data (Excel)" if not st.session_state.get("dev_file_name") else "Replace data (Excel)"
335
  train_test_file = st.sidebar.file_uploader(dev_label, type=["xlsx","xls"], key="dev_upload")
336
 
337
+ # Detect new/changed file by signature (name + size)
338
  if train_test_file is not None:
339
+ try:
340
+ size = train_test_file.size
341
+ except Exception:
342
+ size = len(train_test_file.getvalue())
343
+ sig = (train_test_file.name, size)
344
+ if sig != st.session_state.dev_file_signature:
345
+ # NEW upload => reset state
346
+ st.session_state.dev_file_signature = sig
347
+ st.session_state.dev_file_name = train_test_file.name
348
+ _book_tmp = read_book(train_test_file)
349
+ if _book_tmp:
350
+ first_df = next(iter(_book_tmp.values()))
351
+ st.sidebar.caption(f"**Data loaded:** {train_test_file.name} • {first_df.shape[0]} rows × {first_df.shape[1]} cols")
352
+ st.session_state.dev_file_loaded = True
353
+ st.session_state.dev_previewed = False
354
+ st.session_state.dev_ready = False
355
+ else:
356
+ # Same file, keep existing state
357
  st.session_state.dev_file_loaded = True
 
 
 
358
 
359
+ # Sidebar actions
360
  preview_btn = st.sidebar.button("Preview data", use_container_width=True, disabled=not st.session_state.dev_file_loaded)
361
  if preview_btn and st.session_state.dev_file_loaded and train_test_file is not None:
362
+ st.session_state.dev_preview_request = True # defer opening modal until after helper renders
 
 
363
 
364
  run_btn = st.sidebar.button("Run Model", type="primary", use_container_width=True)
365
 
366
+ proceed_clicked = st.sidebar.button(
 
367
  "Proceed to Prediction ▶",
368
  use_container_width=True,
369
+ disabled=not st.session_state.dev_ready
 
370
  )
371
+ if proceed_clicked and st.session_state.dev_ready:
372
+ st.session_state.app_step = "predict"; st.rerun()
373
+
374
+ # ----- ALWAYS-ON TOP: Title + helper -----
375
+ helper_top = st.container()
376
+ with helper_top:
377
+ st.subheader("Model Development")
378
+ if st.session_state.dev_ready:
379
+ st.success("Case has been built and results are displayed below.")
380
+ elif st.session_state.dev_file_loaded and st.session_state.dev_previewed:
381
+ st.info("Previewed ✓ — now click **Run Model** to build the case.")
382
+ elif st.session_state.dev_file_loaded:
383
+ st.info("📄 **Preview uploaded data** using the sidebar button, then click **Run Model**.")
384
+ else:
385
+ st.write("**Upload your data to build a case, then run the model to review development performance.**")
386
+
387
+ # If user clicked preview, open modal *after* helper so helper stays on top
388
+ if st.session_state.dev_preview_request and train_test_file is not None:
389
+ _book = read_book(train_test_file)
390
+ st.session_state.dev_previewed = True
391
+ st.session_state.dev_preview_request = False
392
+ preview_modal_dev(_book, FEATURES)
393
 
394
+ # Run model
 
 
 
 
 
 
 
 
 
 
395
  if run_btn and train_test_file is not None:
396
  with st.status("Processing…", expanded=False) as status:
397
  book = read_book(train_test_file)
398
  if not book: status.update(label="Failed to read workbook.", state="error"); st.stop()
399
  status.update(label="Workbook read ✓")
 
 
400
  sh_train = find_sheet(book, ["Train","Training","training2","train","training"])
401
  sh_test = find_sheet(book, ["Test","Testing","testing2","test","testing"])
402
  if sh_train is None or sh_test is None:
403
  status.update(label="Workbook must include Train/Training/training2 and Test/Testing/testing2.", state="error"); st.stop()
 
404
  df_tr = book[sh_train].copy(); df_te = book[sh_test].copy()
405
  if not (ensure_cols(df_tr, FEATURES + [TARGET]) and ensure_cols(df_te, FEATURES + [TARGET])):
406
  status.update(label="Missing required columns.", state="error"); st.stop()
 
407
  status.update(label="Columns validated ✓"); status.update(label="Predicting…")
408
 
409
  df_tr["UCS_Pred"] = model.predict(df_tr[FEATURES])
 
424
  tr_min = df_tr[FEATURES].min().to_dict(); tr_max = df_tr[FEATURES].max().to_dict()
425
  st.session_state.train_ranges = {f:(float(tr_min[f]), float(tr_max[f])) for f in FEATURES}
426
 
427
+ st.session_state.dev_ready = True
428
  status.update(label="Done ✓", state="complete"); toast("Model run complete 🚀")
429
+ st.rerun()
430
 
431
+ # Results (if available)
432
  if ("Train" in st.session_state.results) or ("Test" in st.session_state.results):
433
  tab1, tab2 = st.tabs(["Training", "Testing"])
434
  if "Train" in st.session_state.results:
 
477
  val_label = "Upload Validation Excel" if not st.session_state.get("val_file_name") else "Replace data (Excel)"
478
  validation_file = st.sidebar.file_uploader(val_label, type=["xlsx","xls"], key="val_upload")
479
  if validation_file is not None:
 
480
  _book_tmp = read_book(validation_file)
481
  if _book_tmp:
482
  first_df = next(iter(_book_tmp.values()))
 
552
  else:
553
  st.info("Actual UCS values are not available in the validation data. Cross-plot cannot be generated.")
554
  with right:
555
+ st.pyplot(depth_or_index_track(
556
+ st.session_state.results["Validate"], title=None,
557
+ include_actual=(TARGET in st.session_state.results["Validate"].columns)
558
+ ), use_container_width=True)
 
 
 
 
559
 
560
  if oor_table is not None:
561
  st.write("*Out-of-range rows (vs. Training min–max):*")
 
577
  st.warning(str(e))
578
 
579
  # =========================
580
+ # Footer
581
  # =========================
582
  st.markdown("---")
583
  st.markdown(
584
  "<div style='text-align:center; color:#6b7280;'>"
585
  "ST_GeoMech_UCS • © Smart Thinking • "
586
+ "Visit our Website: "
587
+ "<a href='https://www.smartthinking.com.sa' target='_blank'>Visit our Website: smartthinking.com.sa</a>"
588
  "</div>",
589
  unsafe_allow_html=True
590
  )