Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -45,10 +45,11 @@ CANDS = {
|
|
| 45 |
COLORS = {"pred_bo":"#1f77b4","pred_bd":"#d62728","actual_bo":"#f2b702","actual_bd":"#2ca02c","ref":"#5a5a5a"}
|
| 46 |
|
| 47 |
# Plot sizing
|
| 48 |
-
CROSS_W, CROSS_H = 350, 350
|
| 49 |
TRACK_H, TRACK_W = 1000, 500
|
| 50 |
FONT_SZ = 13
|
| 51 |
BOLD_FONT = "Arial Black, Arial, sans-serif"
|
|
|
|
| 52 |
|
| 53 |
# =========================
|
| 54 |
# Page / CSS
|
|
@@ -319,8 +320,10 @@ def cross_plot_static(actual, pred, xlabel, ylabel, color="#1f77b4"):
|
|
| 319 |
ax.set_xticks(ticks); ax.set_yticks(ticks); ax.set_aspect("equal", adjustable="box")
|
| 320 |
fmt = FuncFormatter(lambda x, _: f"{x:.2f}")
|
| 321 |
ax.xaxis.set_major_formatter(fmt); ax.yaxis.set_major_formatter(fmt)
|
| 322 |
-
ax.set_xlabel(xlabel, fontweight="bold", fontsize=10
|
| 323 |
-
ax.
|
|
|
|
|
|
|
| 324 |
for s in ax.spines.values(): s.set_linewidth(1.1); s.set_color("#444")
|
| 325 |
fig.subplots_adjust(left=0.16, bottom=0.16, right=0.98, top=0.98); return fig
|
| 326 |
|
|
@@ -362,14 +365,21 @@ def track_plot_single(df, pred_col, actual_col=None, title_suffix=""):
|
|
| 362 |
legend=dict(x=0.98, y=0.05, xanchor="right", yanchor="bottom",
|
| 363 |
bgcolor="rgba(255,255,255,0.75)", bordercolor="#ccc", borderwidth=1),
|
| 364 |
legend_title_text="", title=title_suffix)
|
| 365 |
-
fig.update_xaxes(
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
return fig
|
| 374 |
|
| 375 |
def track_plot_combined(df):
|
|
@@ -401,14 +411,21 @@ def track_plot_combined(df):
|
|
| 401 |
legend=dict(x=0.98, y=0.05, xanchor="right", yanchor="bottom",
|
| 402 |
bgcolor="rgba(255,255,255,0.75)", bordercolor="#ccc", borderwidth=1),
|
| 403 |
legend_title_text="", title="Combined (Breakout / Breakdown)")
|
| 404 |
-
fig.update_xaxes(
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 412 |
return fig
|
| 413 |
|
| 414 |
def preview_tracks(df: pd.DataFrame, cols: list[str]):
|
|
@@ -463,7 +480,6 @@ def _try_load_or_explain(p: Path, name: str):
|
|
| 463 |
try:
|
| 464 |
return load_model(str(p))
|
| 465 |
except Exception as e:
|
| 466 |
-
# Prefer BO meta versions if available, else BD
|
| 467 |
want_np = (meta_bo.get("versions",{}) or meta_bd.get("versions",{})).get("numpy", "N/A")
|
| 468 |
want_skl = (meta_bo.get("versions",{}) or meta_bd.get("versions",{})).get("scikit_learn", "N/A")
|
| 469 |
st.error(
|
|
@@ -473,11 +489,9 @@ def _try_load_or_explain(p: Path, name: str):
|
|
| 473 |
)
|
| 474 |
st.stop()
|
| 475 |
|
| 476 |
-
# Unwrap payload-style joblibs: {"model": pipeline, "model_info": {...}}
|
| 477 |
def _unwrap(payload):
|
| 478 |
if isinstance(payload, dict) and "model" in payload:
|
| 479 |
return payload["model"], payload.get("model_info", {})
|
| 480 |
-
# Fallback: estimator saved bare or custom object
|
| 481 |
return payload, getattr(payload, "model_info", {})
|
| 482 |
|
| 483 |
payload_bo = _try_load_or_explain(bo_model_path, "BO model")
|
|
@@ -486,23 +500,17 @@ payload_bd = _try_load_or_explain(bd_model_path, "BD model")
|
|
| 486 |
model_bo, info_bo = _unwrap(payload_bo)
|
| 487 |
model_bd, info_bd = _unwrap(payload_bd)
|
| 488 |
|
| 489 |
-
# Resolve features per model (prefer model_info → meta → fallback)
|
| 490 |
features_bo = list((info_bo.get("features") or meta_bo.get("features") or FEATURES_DEFAULT))
|
| 491 |
features_bd = list((info_bd.get("features") or meta_bd.get("features") or FEATURES_DEFAULT))
|
| 492 |
-
|
| 493 |
-
# Use the UNION so BD's 'Depth (ft)' is required and both models can run from one input
|
| 494 |
-
# Order: keep BO order, then append BD-only columns in order
|
| 495 |
features_union = list(dict.fromkeys(features_bo + [c for c in features_bd if c not in features_bo]))
|
| 496 |
|
| 497 |
-
# Resolve targets / units
|
| 498 |
TARGET_BO = str(meta_bo.get("target") or TARGET_BO_DEFAULT)
|
| 499 |
TARGET_BD = str(meta_bd.get("target") or TARGET_BD_DEFAULT)
|
| 500 |
X_UNITS = str(meta_bo.get("units") or meta_bd.get("units") or "MW (pcf)")
|
| 501 |
|
| 502 |
-
# Stash in session
|
| 503 |
st.session_state["FEATURES_BO"] = features_bo
|
| 504 |
st.session_state["FEATURES_BD"] = features_bd
|
| 505 |
-
st.session_state["FEATURES"] = features_union
|
| 506 |
st.session_state["TARGET_BO"] = TARGET_BO
|
| 507 |
st.session_state["TARGET_BD"] = TARGET_BD
|
| 508 |
st.session_state["X_UNITS"] = X_UNITS
|
|
@@ -601,7 +609,7 @@ if st.session_state.app_step == "dev":
|
|
| 601 |
sticky_header("Case Building", "📄 **Preview** then click **Run Model**.")
|
| 602 |
else:
|
| 603 |
sticky_header("Case Building", "**Upload your data** and run the model.")
|
| 604 |
-
|
| 605 |
|
| 606 |
if run and st.session_state.dev_file_bytes:
|
| 607 |
book = read_book_bytes(st.session_state.dev_file_bytes)
|
|
@@ -643,13 +651,35 @@ if st.session_state.app_step == "dev":
|
|
| 643 |
t1, t2, t3 = st.tabs(["Breakout", "Breakdown", "Combined"])
|
| 644 |
units = st.session_state.get("X_UNITS","MW (pcf)")
|
| 645 |
with t1:
|
| 646 |
-
st.
|
| 647 |
-
|
| 648 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 649 |
with t2:
|
| 650 |
-
st.
|
| 651 |
-
|
| 652 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 653 |
with t3:
|
| 654 |
st.plotly_chart(track_plot_combined(df), use_container_width=False, config={"displayModeBar": False, "scrollZoom": True})
|
| 655 |
|
|
@@ -679,7 +709,7 @@ if st.session_state.app_step == "validate":
|
|
| 679 |
if st.sidebar.button("Proceed to Prediction ▶", use_container_width=True): st.session_state.app_step="predict"; st.rerun()
|
| 680 |
|
| 681 |
sticky_header("Validate the Models", "Upload a dataset with the same **feature** columns and **BO/BD** actuals.")
|
| 682 |
-
|
| 683 |
|
| 684 |
if go_btn and up is not None:
|
| 685 |
book = read_book_bytes(up.getvalue())
|
|
@@ -717,15 +747,35 @@ if st.session_state.app_step == "validate":
|
|
| 717 |
t1, t2, t3 = st.tabs(["Breakout", "Breakdown", "Combined"])
|
| 718 |
units = st.session_state.get("X_UNITS","MW (pcf)")
|
| 719 |
with t1:
|
| 720 |
-
st.
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 724 |
with t2:
|
| 725 |
-
st.
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 729 |
with t3:
|
| 730 |
st.plotly_chart(track_plot_combined(df), use_container_width=False, config={"displayModeBar": False, "scrollZoom": True})
|
| 731 |
|
|
@@ -752,7 +802,7 @@ if st.session_state.app_step == "predict":
|
|
| 752 |
if st.sidebar.button("⬅ Back to Case Building", use_container_width=True): st.session_state.app_step="dev"; st.rerun()
|
| 753 |
|
| 754 |
sticky_header("Prediction", "Upload a dataset with **feature columns only** (no BO/BD actuals).")
|
| 755 |
-
|
| 756 |
|
| 757 |
if go_btn and up is not None:
|
| 758 |
book = read_book_bytes(up.getvalue()); name = list(book.keys())[0]
|
|
@@ -814,7 +864,6 @@ if st.session_state.show_preview_modal:
|
|
| 814 |
for t, name in zip(tabs, names):
|
| 815 |
with t:
|
| 816 |
df = book_to_preview[name]
|
| 817 |
-
# show a strict check summary, but do not stop
|
| 818 |
missing = [c for c in st.session_state["FEATURES"] if c not in df.columns]
|
| 819 |
st.write("**Missing vs required features:**", missing if missing else "None ✅")
|
| 820 |
t1, t2 = st.tabs(["Tracks", "Summary"])
|
|
|
|
| 45 |
COLORS = {"pred_bo":"#1f77b4","pred_bd":"#d62728","actual_bo":"#f2b702","actual_bd":"#2ca02c","ref":"#5a5a5a"}
|
| 46 |
|
| 47 |
# Plot sizing
|
| 48 |
+
CROSS_W, CROSS_H = 350, 350 # X-plot size
|
| 49 |
TRACK_H, TRACK_W = 1000, 500
|
| 50 |
FONT_SZ = 13
|
| 51 |
BOLD_FONT = "Arial Black, Arial, sans-serif"
|
| 52 |
+
PLAIN_FONT = "Arial, sans-serif"
|
| 53 |
|
| 54 |
# =========================
|
| 55 |
# Page / CSS
|
|
|
|
| 320 |
ax.set_xticks(ticks); ax.set_yticks(ticks); ax.set_aspect("equal", adjustable="box")
|
| 321 |
fmt = FuncFormatter(lambda x, _: f"{x:.2f}")
|
| 322 |
ax.xaxis.set_major_formatter(fmt); ax.yaxis.set_major_formatter(fmt)
|
| 323 |
+
ax.set_xlabel(xlabel, fontweight="bold", fontsize=10, color="black")
|
| 324 |
+
ax.set_ylabel(ylabel, fontweight="bold", fontsize=10, color="black")
|
| 325 |
+
ax.tick_params(labelsize=6, colors="black")
|
| 326 |
+
ax.grid(True, linestyle=":", alpha=0.3)
|
| 327 |
for s in ax.spines.values(): s.set_linewidth(1.1); s.set_color("#444")
|
| 328 |
fig.subplots_adjust(left=0.16, bottom=0.16, right=0.98, top=0.98); return fig
|
| 329 |
|
|
|
|
| 365 |
legend=dict(x=0.98, y=0.05, xanchor="right", yanchor="bottom",
|
| 366 |
bgcolor="rgba(255,255,255,0.75)", bordercolor="#ccc", borderwidth=1),
|
| 367 |
legend_title_text="", title=title_suffix)
|
| 368 |
+
fig.update_xaxes(
|
| 369 |
+
title_text=st.session_state.get("X_UNITS","MW (pcf)"),
|
| 370 |
+
title_font=dict(size=20, family=BOLD_FONT, color="#000"),
|
| 371 |
+
tickfont=dict(size=15, family=PLAIN_FONT, color="#000"),
|
| 372 |
+
side="top", range=[xmin, xmax], ticks="outside", tickformat=",.2f",
|
| 373 |
+
tickmode="auto", tick0=tick0, showline=True, linewidth=1.2, linecolor="#444",
|
| 374 |
+
mirror=True, showgrid=True, gridcolor="rgba(0,0,0,0.12)", automargin=True
|
| 375 |
+
)
|
| 376 |
+
fig.update_yaxes(
|
| 377 |
+
title_text=ylab,
|
| 378 |
+
title_font=dict(size=20, family=BOLD_FONT, color="#000"),
|
| 379 |
+
tickfont=dict(size=15, family=PLAIN_FONT, color="#000"),
|
| 380 |
+
range=y_range, ticks="outside", showline=True, linewidth=1.2, linecolor="#444",
|
| 381 |
+
mirror=True, showgrid=True, gridcolor="rgba(0,0,0,0.12)", automargin=True
|
| 382 |
+
)
|
| 383 |
return fig
|
| 384 |
|
| 385 |
def track_plot_combined(df):
|
|
|
|
| 411 |
legend=dict(x=0.98, y=0.05, xanchor="right", yanchor="bottom",
|
| 412 |
bgcolor="rgba(255,255,255,0.75)", bordercolor="#ccc", borderwidth=1),
|
| 413 |
legend_title_text="", title="Combined (Breakout / Breakdown)")
|
| 414 |
+
fig.update_xaxes(
|
| 415 |
+
title_text=st.session_state.get("X_UNITS","MW (pcf)"),
|
| 416 |
+
title_font=dict(size=20, family=BOLD_FONT, color="#000"),
|
| 417 |
+
tickfont=dict(size=15, family=PLAIN_FONT, color="#000"),
|
| 418 |
+
side="top", range=[xmin, xmax], ticks="outside", tickformat=",.2f",
|
| 419 |
+
tickmode="auto", tick0=tick0, showline=True, linewidth=1.2, linecolor="#444",
|
| 420 |
+
mirror=True, showgrid=True, gridcolor="rgba(0,0,0,0.12)", automargin=True
|
| 421 |
+
)
|
| 422 |
+
fig.update_yaxes(
|
| 423 |
+
title_text=ylab,
|
| 424 |
+
title_font=dict(size=20, family=BOLD_FONT, color="#000"),
|
| 425 |
+
tickfont=dict(size=15, family=PLAIN_FONT, color="#000"),
|
| 426 |
+
range=y_range, ticks="outside", showline=True, linewidth=1.2, linecolor="#444",
|
| 427 |
+
mirror=True, showgrid=True, gridcolor="rgba(0,0,0,0.12)", automargin=True
|
| 428 |
+
)
|
| 429 |
return fig
|
| 430 |
|
| 431 |
def preview_tracks(df: pd.DataFrame, cols: list[str]):
|
|
|
|
| 480 |
try:
|
| 481 |
return load_model(str(p))
|
| 482 |
except Exception as e:
|
|
|
|
| 483 |
want_np = (meta_bo.get("versions",{}) or meta_bd.get("versions",{})).get("numpy", "N/A")
|
| 484 |
want_skl = (meta_bo.get("versions",{}) or meta_bd.get("versions",{})).get("scikit_learn", "N/A")
|
| 485 |
st.error(
|
|
|
|
| 489 |
)
|
| 490 |
st.stop()
|
| 491 |
|
|
|
|
| 492 |
def _unwrap(payload):
|
| 493 |
if isinstance(payload, dict) and "model" in payload:
|
| 494 |
return payload["model"], payload.get("model_info", {})
|
|
|
|
| 495 |
return payload, getattr(payload, "model_info", {})
|
| 496 |
|
| 497 |
payload_bo = _try_load_or_explain(bo_model_path, "BO model")
|
|
|
|
| 500 |
model_bo, info_bo = _unwrap(payload_bo)
|
| 501 |
model_bd, info_bd = _unwrap(payload_bd)
|
| 502 |
|
|
|
|
| 503 |
features_bo = list((info_bo.get("features") or meta_bo.get("features") or FEATURES_DEFAULT))
|
| 504 |
features_bd = list((info_bd.get("features") or meta_bd.get("features") or FEATURES_DEFAULT))
|
|
|
|
|
|
|
|
|
|
| 505 |
features_union = list(dict.fromkeys(features_bo + [c for c in features_bd if c not in features_bo]))
|
| 506 |
|
|
|
|
| 507 |
TARGET_BO = str(meta_bo.get("target") or TARGET_BO_DEFAULT)
|
| 508 |
TARGET_BD = str(meta_bd.get("target") or TARGET_BD_DEFAULT)
|
| 509 |
X_UNITS = str(meta_bo.get("units") or meta_bd.get("units") or "MW (pcf)")
|
| 510 |
|
|
|
|
| 511 |
st.session_state["FEATURES_BO"] = features_bo
|
| 512 |
st.session_state["FEATURES_BD"] = features_bd
|
| 513 |
+
st.session_state["FEATURES"] = features_union
|
| 514 |
st.session_state["TARGET_BO"] = TARGET_BO
|
| 515 |
st.session_state["TARGET_BD"] = TARGET_BD
|
| 516 |
st.session_state["X_UNITS"] = X_UNITS
|
|
|
|
| 609 |
sticky_header("Case Building", "📄 **Preview** then click **Run Model**.")
|
| 610 |
else:
|
| 611 |
sticky_header("Case Building", "**Upload your data** and run the model.")
|
| 612 |
+
|
| 613 |
|
| 614 |
if run and st.session_state.dev_file_bytes:
|
| 615 |
book = read_book_bytes(st.session_state.dev_file_bytes)
|
|
|
|
| 651 |
t1, t2, t3 = st.tabs(["Breakout", "Breakdown", "Combined"])
|
| 652 |
units = st.session_state.get("X_UNITS","MW (pcf)")
|
| 653 |
with t1:
|
| 654 |
+
left, right = st.columns([3,1], gap="large")
|
| 655 |
+
with left:
|
| 656 |
+
st.plotly_chart(
|
| 657 |
+
track_plot_single(df, PRED_BO, actual_col=TARGET_BO, title_suffix="Breakout"),
|
| 658 |
+
use_container_width=False, config={"displayModeBar": False, "scrollZoom": True}
|
| 659 |
+
)
|
| 660 |
+
with right:
|
| 661 |
+
st.pyplot(
|
| 662 |
+
cross_plot_static(df[TARGET_BO], df[PRED_BO],
|
| 663 |
+
f"Actual {TARGET_BO} ({units})",
|
| 664 |
+
f"Predicted {TARGET_BO} ({units})",
|
| 665 |
+
COLORS["pred_bo"]),
|
| 666 |
+
use_container_width=False
|
| 667 |
+
)
|
| 668 |
with t2:
|
| 669 |
+
left, right = st.columns([3,1], gap="large")
|
| 670 |
+
with left:
|
| 671 |
+
st.plotly_chart(
|
| 672 |
+
track_plot_single(df, PRED_BD, actual_col=TARGET_BD, title_suffix="Breakdown"),
|
| 673 |
+
use_container_width=False, config={"displayModeBar": False, "scrollZoom": True}
|
| 674 |
+
)
|
| 675 |
+
with right:
|
| 676 |
+
st.pyplot(
|
| 677 |
+
cross_plot_static(df[TARGET_BD], df[PRED_BD],
|
| 678 |
+
f"Actual {TARGET_BD} ({units})",
|
| 679 |
+
f"Predicted {TARGET_BD} ({units})",
|
| 680 |
+
COLORS["pred_bd"]),
|
| 681 |
+
use_container_width=False
|
| 682 |
+
)
|
| 683 |
with t3:
|
| 684 |
st.plotly_chart(track_plot_combined(df), use_container_width=False, config={"displayModeBar": False, "scrollZoom": True})
|
| 685 |
|
|
|
|
| 709 |
if st.sidebar.button("Proceed to Prediction ▶", use_container_width=True): st.session_state.app_step="predict"; st.rerun()
|
| 710 |
|
| 711 |
sticky_header("Validate the Models", "Upload a dataset with the same **feature** columns and **BO/BD** actuals.")
|
| 712 |
+
|
| 713 |
|
| 714 |
if go_btn and up is not None:
|
| 715 |
book = read_book_bytes(up.getvalue())
|
|
|
|
| 747 |
t1, t2, t3 = st.tabs(["Breakout", "Breakdown", "Combined"])
|
| 748 |
units = st.session_state.get("X_UNITS","MW (pcf)")
|
| 749 |
with t1:
|
| 750 |
+
left, right = st.columns([3,1], gap="large")
|
| 751 |
+
with left:
|
| 752 |
+
st.plotly_chart(
|
| 753 |
+
track_plot_single(df, PRED_BO, actual_col=TARGET_BO, title_suffix="Breakout"),
|
| 754 |
+
use_container_width=False, config={"displayModeBar": False, "scrollZoom": True}
|
| 755 |
+
)
|
| 756 |
+
with right:
|
| 757 |
+
st.pyplot(
|
| 758 |
+
cross_plot_static(df[TARGET_BO], df[PRED_BO],
|
| 759 |
+
f"Actual {TARGET_BO} ({units})",
|
| 760 |
+
f"Predicted {TARGET_BO} ({units})",
|
| 761 |
+
COLORS["pred_bo"]),
|
| 762 |
+
use_container_width=False
|
| 763 |
+
)
|
| 764 |
with t2:
|
| 765 |
+
left, right = st.columns([3,1], gap="large")
|
| 766 |
+
with left:
|
| 767 |
+
st.plotly_chart(
|
| 768 |
+
track_plot_single(df, PRED_BD, actual_col=TARGET_BD, title_suffix="Breakdown"),
|
| 769 |
+
use_container_width=False, config={"displayModeBar": False, "scrollZoom": True}
|
| 770 |
+
)
|
| 771 |
+
with right:
|
| 772 |
+
st.pyplot(
|
| 773 |
+
cross_plot_static(df[TARGET_BD], df[PRED_BD],
|
| 774 |
+
f"Actual {TARGET_BD} ({units})",
|
| 775 |
+
f"Predicted {TARGET_BD} ({units})",
|
| 776 |
+
COLORS["pred_bd"]),
|
| 777 |
+
use_container_width=False
|
| 778 |
+
)
|
| 779 |
with t3:
|
| 780 |
st.plotly_chart(track_plot_combined(df), use_container_width=False, config={"displayModeBar": False, "scrollZoom": True})
|
| 781 |
|
|
|
|
| 802 |
if st.sidebar.button("⬅ Back to Case Building", use_container_width=True): st.session_state.app_step="dev"; st.rerun()
|
| 803 |
|
| 804 |
sticky_header("Prediction", "Upload a dataset with **feature columns only** (no BO/BD actuals).")
|
| 805 |
+
|
| 806 |
|
| 807 |
if go_btn and up is not None:
|
| 808 |
book = read_book_bytes(up.getvalue()); name = list(book.keys())[0]
|
|
|
|
| 864 |
for t, name in zip(tabs, names):
|
| 865 |
with t:
|
| 866 |
df = book_to_preview[name]
|
|
|
|
| 867 |
missing = [c for c in st.session_state["FEATURES"] if c not in df.columns]
|
| 868 |
st.write("**Missing vs required features:**", missing if missing else "None ✅")
|
| 869 |
t1, t2 = st.tabs(["Tracks", "Summary"])
|