UCS2014 commited on
Commit
8b4c214
·
verified ·
1 Parent(s): 7f0fa68

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +73 -142
app.py CHANGED
@@ -36,25 +36,15 @@ BOLD_FONT = "Arial Black, Arial, sans-serif" # used for bold axis titles & tick
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
  }
@@ -443,6 +433,11 @@ st.sidebar.markdown(f"""
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,9 +471,7 @@ with st.container():
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,8 +479,7 @@ if st.session_state.app_step == "intro":
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,70 +496,47 @@ if st.session_state.app_step == "dev":
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,18 +552,16 @@ if st.session_state.app_step == "dev":
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,78 +570,54 @@ if st.session_state.app_step == "validate":
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,7 +627,7 @@ if st.session_state.app_step == "validate":
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,52 +636,41 @@ if st.session_state.app_step == "predict":
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,8 +678,7 @@ if st.session_state.app_step == "predict":
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
  # =========================
 
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 for the sticky header and message box
40
  st.markdown("""
41
  <style>
 
 
 
 
 
 
 
 
 
 
42
  /* CSS to make the header sticky */
43
  .fixed-header-container {
44
  position: sticky;
45
  top: 0;
46
  z-index: 1000;
47
+ background-color: #f0f2f6; /* Adjust to match your app's background color */
48
  padding: 10px 0;
49
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
50
  }
 
433
  """, unsafe_allow_html=True
434
  )
435
 
436
+ # =========================
437
+ # Reusable Sticky Header Function
438
+ # This function is now removed, replaced by the fixed-header-container
439
+ # =========================
440
+
441
  # =========================
442
  # The main sticky container is defined here, before the app logic
443
  # This ensures it's rendered at the top of the page regardless of state.
 
471
  # =========================
472
  if st.session_state.app_step == "intro":
473
  st.header("Welcome!")
474
+ st.markdown("This software is developed by *Smart Thinking AI-Solutions Team* to estimate UCS from drilling data.")
 
 
475
  st.subheader("How It Works")
476
  st.markdown(
477
  "1) **Upload your data to build the case and preview the performance of our model.** \n"
 
479
  "3) **Proceed to Validation** (with actual UCS) or **Proceed to Prediction** (no UCS)."
480
  )
481
  if st.button("Start Showcase", type="primary"):
482
+ st.session_state.app_step = "dev"; st.rerun()
 
483
 
484
  # =========================
485
  # CASE BUILDING
 
496
  tmp = read_book_bytes(st.session_state.dev_file_bytes)
497
  if tmp:
498
  df0 = next(iter(tmp.values()))
499
+ st.sidebar.caption(f"**Data loaded:** {st.session_state.dev_file_name} • {df0.shape[0]} rows × {df0.shape[1]} cols")
 
 
500
 
501
+ if st.sidebar.button("Preview data", use_container_width=True, disabled=not st.session_state.dev_file_loaded):
 
 
 
 
502
  preview_modal(read_book_bytes(st.session_state.dev_file_bytes))
503
  st.session_state.dev_preview = True
504
 
505
  run = st.sidebar.button("Run Model", type="primary", use_container_width=True)
506
+ if st.sidebar.button("Proceed to Validation ▶", use_container_width=True): st.session_state.app_step="validate"; st.rerun()
507
+ if st.sidebar.button("Proceed to Prediction ▶", use_container_width=True): st.session_state.app_step="predict"; st.rerun()
 
 
 
 
508
 
509
  if run and st.session_state.dev_file_bytes:
510
  book = read_book_bytes(st.session_state.dev_file_bytes)
511
+ sh_train = find_sheet(book, ["Train","Training","training2","train","training"])
512
+ sh_test = find_sheet(book, ["Test","Testing","testing2","test","testing"])
 
 
 
 
513
  if sh_train is None or sh_test is None:
514
+ st.error("Workbook must include Train/Training/training2 and Test/Testing/testing2 sheets."); st.stop()
515
+ tr = book[sh_train].copy(); te = book[sh_test].copy()
516
+ if not (ensure_cols(tr, FEATURES+[TARGET]) and ensure_cols(te, FEATURES+[TARGET])):
517
+ st.error("Missing required columns."); st.stop()
 
 
 
 
 
518
  tr["UCS_Pred"] = model.predict(tr[FEATURES])
519
  te["UCS_Pred"] = model.predict(te[FEATURES])
520
 
521
+ st.session_state.results["Train"]=tr; st.session_state.results["Test"]=te
522
+ st.session_state.results["m_train"]={
 
523
  "R": pearson_r(tr[TARGET], tr["UCS_Pred"]),
524
  "RMSE": rmse(tr[TARGET], tr["UCS_Pred"]),
525
  "MAE": mean_absolute_error(tr[TARGET], tr["UCS_Pred"])
526
  }
527
+ st.session_state.results["m_test"]={
528
  "R": pearson_r(te[TARGET], te["UCS_Pred"]),
529
  "RMSE": rmse(te[TARGET], te["UCS_Pred"]),
530
  "MAE": mean_absolute_error(te[TARGET], te["UCS_Pred"])
531
  }
532
 
533
+ tr_min = tr[FEATURES].min().to_dict(); tr_max = tr[FEATURES].max().to_dict()
534
+ st.session_state.train_ranges = {f:(float(tr_min[f]), float(tr_max[f])) for f in FEATURES}
 
535
  st.success("Case has been built and results are displayed below.")
536
 
537
  def _dev_block(df, m):
538
+ c1,c2,c3 = st.columns(3)
539
+ c1.metric("R", f"{m['R']:.2f}"); c2.metric("RMSE", f"{m['RMSE']:.2f}"); c3.metric("MAE", f"{m['MAE']:.2f}")
 
 
540
 
541
  # 2-column layout, big gap (prevents overlap)
542
  col_cross, col_track = st.columns([3, 2], gap="large")
 
552
  if "Train" in st.session_state.results or "Test" in st.session_state.results:
553
  tab1, tab2 = st.tabs(["Training", "Testing"])
554
  if "Train" in st.session_state.results:
555
+ with tab1: _dev_block(st.session_state.results["Train"], st.session_state.results["m_train"])
 
556
  if "Test" in st.session_state.results:
557
+ with tab2: _dev_block(st.session_state.results["Test"], st.session_state.results["m_test"])
 
558
 
559
  # =========================
560
  # VALIDATION (with actual UCS)
561
  # =========================
562
  if st.session_state.app_step == "validate":
563
  st.sidebar.header("Validate the Model")
564
+ up = st.sidebar.file_uploader("Upload Validation Excel", type=["xlsx","xls"])
565
  if up is not None:
566
  book = read_book_bytes(up.getvalue())
567
  if book:
 
570
  if st.sidebar.button("Preview data", use_container_width=True, disabled=(up is None)):
571
  preview_modal(read_book_bytes(up.getvalue()))
572
  go_btn = st.sidebar.button("Predict", type="primary", use_container_width=True)
573
+ if st.sidebar.button("⬅ Back to Case Building", use_container_width=True): st.session_state.app_step="dev"; st.rerun()
574
+ if st.sidebar.button("Proceed to Prediction ▶", use_container_width=True): st.session_state.app_step="predict"; st.rerun()
 
 
 
 
575
 
576
  if go_btn and up is not None:
577
  book = read_book_bytes(up.getvalue())
578
+ name = find_sheet(book, ["Validation","Validate","validation2","Val","val"]) or list(book.keys())[0]
579
  df = book[name].copy()
580
+ if not ensure_cols(df, FEATURES+[TARGET]): st.error("Missing required columns."); st.stop()
 
 
581
  df["UCS_Pred"] = model.predict(df[FEATURES])
582
+ st.session_state.results["Validate"]=df
583
 
584
+ ranges = st.session_state.train_ranges; oor_pct = 0.0; tbl=None
 
 
585
  if ranges:
586
+ any_viol = pd.DataFrame({f:(df[f]<ranges[f][0])|(df[f]>ranges[f][1]) for f in FEATURES}).any(axis=1)
587
+ oor_pct = float(any_viol.mean()*100.0)
 
 
588
  if any_viol.any():
589
  tbl = df.loc[any_viol, FEATURES].copy()
590
  for c in FEATURES:
591
+ if pd.api.types.is_numeric_dtype(tbl[c]): tbl[c] = tbl[c].round(2)
592
+ tbl["Violations"] = pd.DataFrame({f:(df[f]<ranges[f][0])|(df[f]>ranges[f][1]) for f in FEATURES}).loc[any_viol].apply(lambda r:", ".join([c for c,v in r.items() if v]), axis=1)
593
+ st.session_state.results["m_val"]={
 
 
 
594
  "R": pearson_r(df[TARGET], df["UCS_Pred"]),
595
  "RMSE": rmse(df[TARGET], df["UCS_Pred"]),
596
  "MAE": mean_absolute_error(df[TARGET], df["UCS_Pred"])
597
  }
598
+ st.session_state.results["sv_val"]={"n":len(df),"pred_min":float(df["UCS_Pred"].min()),"pred_max":float(df["UCS_Pred"].max()),"oor":oor_pct}
599
+ st.session_state.results["oor_tbl"]=tbl
 
 
 
 
 
600
 
601
  if "Validate" in st.session_state.results:
602
  m = st.session_state.results["m_val"]
603
+ c1,c2,c3 = st.columns(3)
604
+ c1.metric("R", f"{m['R']:.2f}"); c2.metric("RMSE", f"{m['RMSE']:.2f}"); c3.metric("MAE", f"{m['MAE']:.2f}")
 
 
605
 
606
  col_cross, col_track = st.columns([3, 2], gap="large")
607
  with col_cross:
608
  st.pyplot(
609
+ cross_plot_static(st.session_state.results["Validate"][TARGET],
610
+ st.session_state.results["Validate"]["UCS_Pred"]),
 
 
611
  use_container_width=True
612
  )
613
  with col_track:
614
  st.plotly_chart(
615
  track_plot(st.session_state.results["Validate"], include_actual=True),
616
+ use_container_width=True, config={"displayModeBar": False, "scrollZoom": True}
 
617
  )
618
 
619
  sv = st.session_state.results["sv_val"]
620
+ if sv["oor"] > 0: st.warning("Some inputs fall outside **training min–max** ranges.")
 
621
  if st.session_state.results["oor_tbl"] is not None:
622
  st.write("*Out-of-range rows (vs. Training min–max):*")
623
  df_centered_rounded(st.session_state.results["oor_tbl"])
 
627
  # =========================
628
  if st.session_state.app_step == "predict":
629
  st.sidebar.header("Prediction (No Actual UCS)")
630
+ up = st.sidebar.file_uploader("Upload Prediction Excel", type=["xlsx","xls"])
631
  if up is not None:
632
  book = read_book_bytes(up.getvalue())
633
  if book:
 
636
  if st.sidebar.button("Preview data", use_container_width=True, disabled=(up is None)):
637
  preview_modal(read_book_bytes(up.getvalue()))
638
  go_btn = st.sidebar.button("Predict", type="primary", use_container_width=True)
639
+ if st.sidebar.button("⬅ Back to Case Building", use_container_width=True): st.session_state.app_step="dev"; st.rerun()
 
 
640
 
641
  if go_btn and up is not None:
642
+ book = read_book_bytes(up.getvalue()); name = list(book.keys())[0]
 
643
  df = book[name].copy()
644
+ if not ensure_cols(df, FEATURES): st.error("Missing required columns."); st.stop()
 
 
645
  df["UCS_Pred"] = model.predict(df[FEATURES])
646
+ st.session_state.results["PredictOnly"]=df
647
 
648
+ ranges = st.session_state.train_ranges; oor_pct = 0.0
 
649
  if ranges:
650
+ any_viol = pd.DataFrame({f:(df[f]<ranges[f][0])|(df[f]>ranges[f][1]) for f in FEATURES}).any(axis=1)
651
+ oor_pct = float(any_viol.mean()*100.0)
652
+ st.session_state.results["sv_pred"]={
653
+ "n":len(df),
654
+ "pred_min":float(df["UCS_Pred"].min()),
655
+ "pred_max":float(df["UCS_Pred"].max()),
656
+ "pred_mean":float(df["UCS_Pred"].mean()),
657
+ "pred_std":float(df["UCS_Pred"].std(ddof=0)),
658
+ "oor":oor_pct
 
 
659
  }
660
 
661
  if "PredictOnly" in st.session_state.results:
662
+ df = st.session_state.results["PredictOnly"]; sv = st.session_state.results["sv_pred"]
 
663
 
664
+ col_left, col_right = st.columns([2,3], gap="large")
665
  with col_left:
666
  table = pd.DataFrame({
667
+ "Metric": ["# points","Pred min","Pred max","Pred mean","Pred std","OOR %"],
668
+ "Value": [sv["n"],
669
+ round(sv["pred_min"],2),
670
+ round(sv["pred_max"],2),
671
+ round(sv["pred_mean"],2),
672
+ round(sv["pred_std"],2),
673
+ f'{sv["oor"]:.1f}%']
 
 
674
  })
675
  st.success("Predictions ready ✓")
676
  df_centered_rounded(table, hide_index=True)
 
678
  with col_right:
679
  st.plotly_chart(
680
  track_plot(df, include_actual=False),
681
+ use_container_width=True, config={"displayModeBar": False, "scrollZoom": True}
 
682
  )
683
 
684
  # =========================