Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -93,7 +93,7 @@ div[data-testid="stExpander"] div[data-baseweb="tab-list"] {
|
|
| 93 |
</style>
|
| 94 |
""", unsafe_allow_html=True)
|
| 95 |
|
| 96 |
-
# Center text in
|
| 97 |
TABLE_CENTER_CSS = [
|
| 98 |
dict(selector="th", props=[("text-align", "center")]),
|
| 99 |
dict(selector="td", props=[("text-align", "center")]),
|
|
@@ -162,7 +162,7 @@ def rmse(y_true, y_pred) -> float:
|
|
| 162 |
def pearson_r(y_true, y_pred) -> float:
|
| 163 |
a = np.asarray(y_true, dtype=float)
|
| 164 |
p = np.asarray(y_pred, dtype=float)
|
| 165 |
-
if a.size < 2:
|
| 166 |
return float("nan")
|
| 167 |
return float(np.corrcoef(a, p)[0, 1])
|
| 168 |
|
|
@@ -230,7 +230,7 @@ def _excel_safe_name(name: str) -> str:
|
|
| 230 |
safe = ''.join('_' if ch in bad else ch for ch in str(name))
|
| 231 |
return safe[:31]
|
| 232 |
|
| 233 |
-
def _round_numeric(df: pd.DataFrame, ndigits: int =
|
| 234 |
out = df.copy()
|
| 235 |
for c in out.columns:
|
| 236 |
if pd.api.types.is_float_dtype(out[c]) or pd.api.types.is_integer_dtype(out[c]):
|
|
@@ -267,7 +267,7 @@ def _excel_autofit(writer, sheet_name: str, df: pd.DataFrame, min_w: int = 8, ma
|
|
| 267 |
ws.set_column(i, i, max(min_w, min(max_len + 2, max_w)))
|
| 268 |
ws.freeze_panes(1, 0)
|
| 269 |
|
| 270 |
-
def _add_sheet(sheets: dict, order: list, name: str, df: pd.DataFrame, ndigits: int):
|
| 271 |
if isinstance(df, pd.DataFrame) and not df.empty:
|
| 272 |
sheets[name] = _round_numeric(df, ndigits)
|
| 273 |
order.append(name)
|
|
@@ -283,7 +283,8 @@ def _available_sections():
|
|
| 283 |
sections += ["Info"]
|
| 284 |
return sections
|
| 285 |
|
| 286 |
-
def build_export_workbook(selected: list[str], ndigits: int =
|
|
|
|
| 287 |
res = st.session_state.get("results", {})
|
| 288 |
if not res:
|
| 289 |
return None, None, []
|
|
@@ -358,11 +359,11 @@ def build_export_workbook(selected: list[str], ndigits: int = 2) -> tuple[bytes|
|
|
| 358 |
fname = f"YM_Export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
|
| 359 |
return bio.getvalue(), fname, order
|
| 360 |
|
| 361 |
-
def render_export_button(
|
|
|
|
| 362 |
st.divider()
|
| 363 |
st.markdown("### Export to Excel")
|
| 364 |
|
| 365 |
-
default_sections = _available_sections()
|
| 366 |
all_sections = [
|
| 367 |
"Training","Training_Metrics","Training_Summary",
|
| 368 |
"Testing","Testing_Metrics","Testing_Summary",
|
|
@@ -370,14 +371,17 @@ def render_export_button(key: str = "export_main") -> None:
|
|
| 370 |
"Prediction","Prediction_Summary",
|
| 371 |
"Training_Ranges","Info"
|
| 372 |
]
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
|
|
|
|
|
|
|
|
|
| 381 |
if names:
|
| 382 |
st.caption("Will include: " + ", ".join(names))
|
| 383 |
|
|
@@ -387,7 +391,7 @@ def render_export_button(key: str = "export_main") -> None:
|
|
| 387 |
file_name=((base_name or "YM_Export") + "_" + datetime.now().strftime("%Y%m%d_%H%M%S") + ".xlsx") if data else "YM_Export.xlsx",
|
| 388 |
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
| 389 |
disabled=(data is None),
|
| 390 |
-
key=
|
| 391 |
)
|
| 392 |
|
| 393 |
# =========================
|
|
@@ -510,7 +514,7 @@ def preview_tracks(df: pd.DataFrame, cols: list[str]):
|
|
| 510 |
ax.text(0.5,0.5,"No selected columns",ha="center",va="center"); ax.axis("off")
|
| 511 |
return fig
|
| 512 |
fig, axes = plt.subplots(1, n, figsize=(2.2*n, 7.0), sharey=True, dpi=100)
|
| 513 |
-
if n == 1:
|
| 514 |
axes = [axes]
|
| 515 |
idx = np.arange(1, len(df) + 1)
|
| 516 |
for ax, col in zip(axes, cols):
|
|
@@ -744,6 +748,10 @@ if st.session_state.app_step == "dev":
|
|
| 744 |
st.session_state.train_ranges = {f:(float(tr_min[f]), float(tr_max[f])) for f in FEATURES}
|
| 745 |
st.markdown('<div class="st-message-box st-success">Case has been built and results are displayed below.</div>', unsafe_allow_html=True)
|
| 746 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 747 |
def _dev_block(df, m):
|
| 748 |
c1,c2,c3 = st.columns(3)
|
| 749 |
c1.metric("R", f"{m['R']:.2f}")
|
|
@@ -826,6 +834,13 @@ if st.session_state.app_step == "validate":
|
|
| 826 |
st.session_state.results["sv_val"]={"n":len(df), "pred_min":float(df[PRED_COL].min()), "pred_max":float(df[PRED_COL].max()), "oor":oor_pct}
|
| 827 |
st.session_state.results["oor_tbl"]=tbl
|
| 828 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 829 |
if "Validate" in st.session_state.results:
|
| 830 |
m = st.session_state.results["m_val"]
|
| 831 |
c1,c2,c3 = st.columns(3)
|
|
@@ -902,6 +917,10 @@ if st.session_state.app_step == "predict":
|
|
| 902 |
"oor":oor_pct
|
| 903 |
}
|
| 904 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 905 |
if "PredictOnly" in st.session_state.results:
|
| 906 |
df = st.session_state.results["PredictOnly"]; sv = st.session_state.results["sv_pred"]
|
| 907 |
|
|
@@ -962,11 +981,7 @@ if st.session_state.show_preview_modal:
|
|
| 962 |
df_centered_rounded(tbl)
|
| 963 |
st.session_state.show_preview_modal = False
|
| 964 |
|
| 965 |
-
#
|
| 966 |
-
if st.session_state.app_step in ("dev", "validate", "predict"):
|
| 967 |
-
has_results = any(k in st.session_state.results for k in ("Train", "Test", "Validate", "PredictOnly"))
|
| 968 |
-
if has_results:
|
| 969 |
-
render_export_button(key=f"export_{st.session_state.app_step}")
|
| 970 |
|
| 971 |
# =========================
|
| 972 |
# Footer
|
|
@@ -978,4 +993,4 @@ st.markdown("""
|
|
| 978 |
© 2025 Smart Thinking AI-Solutions Team. All rights reserved.<br>
|
| 979 |
Website: <a href="https://smartthinking.com.sa" target="_blank" rel="noopener noreferrer">smartthinking.com.sa</a>
|
| 980 |
</div>
|
| 981 |
-
""", unsafe_allow_html=True)
|
|
|
|
| 93 |
</style>
|
| 94 |
""", unsafe_allow_html=True)
|
| 95 |
|
| 96 |
+
# Center text in pandas tables
|
| 97 |
TABLE_CENTER_CSS = [
|
| 98 |
dict(selector="th", props=[("text-align", "center")]),
|
| 99 |
dict(selector="td", props=[("text-align", "center")]),
|
|
|
|
| 162 |
def pearson_r(y_true, y_pred) -> float:
|
| 163 |
a = np.asarray(y_true, dtype=float)
|
| 164 |
p = np.asarray(y_pred, dtype=float)
|
| 165 |
+
if a.size < 2:
|
| 166 |
return float("nan")
|
| 167 |
return float(np.corrcoef(a, p)[0, 1])
|
| 168 |
|
|
|
|
| 230 |
safe = ''.join('_' if ch in bad else ch for ch in str(name))
|
| 231 |
return safe[:31]
|
| 232 |
|
| 233 |
+
def _round_numeric(df: pd.DataFrame, ndigits: int = 3) -> pd.DataFrame:
|
| 234 |
out = df.copy()
|
| 235 |
for c in out.columns:
|
| 236 |
if pd.api.types.is_float_dtype(out[c]) or pd.api.types.is_integer_dtype(out[c]):
|
|
|
|
| 267 |
ws.set_column(i, i, max(min_w, min(max_len + 2, max_w)))
|
| 268 |
ws.freeze_panes(1, 0)
|
| 269 |
|
| 270 |
+
def _add_sheet(sheets: dict, order: list, name: str, df: pd.DataFrame, ndigits: int = 3):
|
| 271 |
if isinstance(df, pd.DataFrame) and not df.empty:
|
| 272 |
sheets[name] = _round_numeric(df, ndigits)
|
| 273 |
order.append(name)
|
|
|
|
| 283 |
sections += ["Info"]
|
| 284 |
return sections
|
| 285 |
|
| 286 |
+
def build_export_workbook(selected: list[str], ndigits: int = 3) -> tuple[bytes|None, str|None, list[str]]:
|
| 287 |
+
"""Builds an in-memory Excel workbook for selected sheets; fixed rounding to 3 decimals."""
|
| 288 |
res = st.session_state.get("results", {})
|
| 289 |
if not res:
|
| 290 |
return None, None, []
|
|
|
|
| 359 |
fname = f"YM_Export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
|
| 360 |
return bio.getvalue(), fname, order
|
| 361 |
|
| 362 |
+
def render_export_button(phase_key: str, default_sections: list[str]) -> None:
|
| 363 |
+
"""Export UI — dropdown checklist, fixed rounding=3, appears inside each phase after results."""
|
| 364 |
st.divider()
|
| 365 |
st.markdown("### Export to Excel")
|
| 366 |
|
|
|
|
| 367 |
all_sections = [
|
| 368 |
"Training","Training_Metrics","Training_Summary",
|
| 369 |
"Testing","Testing_Metrics","Testing_Summary",
|
|
|
|
| 371 |
"Prediction","Prediction_Summary",
|
| 372 |
"Training_Ranges","Info"
|
| 373 |
]
|
| 374 |
+
# Dropdown checklist
|
| 375 |
+
selected = st.multiselect(
|
| 376 |
+
"Sheets to include",
|
| 377 |
+
options=all_sections,
|
| 378 |
+
default=default_sections,
|
| 379 |
+
help="Choose which sheets to include in the Excel export.",
|
| 380 |
+
)
|
| 381 |
+
|
| 382 |
+
base_name = st.text_input("Base filename", value="YM_Export", key=f"basename_{phase_key}")
|
| 383 |
+
|
| 384 |
+
data, _, names = build_export_workbook(selected=selected, ndigits=3) # fixed 3 decimals
|
| 385 |
if names:
|
| 386 |
st.caption("Will include: " + ", ".join(names))
|
| 387 |
|
|
|
|
| 391 |
file_name=((base_name or "YM_Export") + "_" + datetime.now().strftime("%Y%m%d_%H%M%S") + ".xlsx") if data else "YM_Export.xlsx",
|
| 392 |
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
| 393 |
disabled=(data is None),
|
| 394 |
+
key=f"download_{phase_key}",
|
| 395 |
)
|
| 396 |
|
| 397 |
# =========================
|
|
|
|
| 514 |
ax.text(0.5,0.5,"No selected columns",ha="center",va="center"); ax.axis("off")
|
| 515 |
return fig
|
| 516 |
fig, axes = plt.subplots(1, n, figsize=(2.2*n, 7.0), sharey=True, dpi=100)
|
| 517 |
+
if n == 1:
|
| 518 |
axes = [axes]
|
| 519 |
idx = np.arange(1, len(df) + 1)
|
| 520 |
for ax, col in zip(axes, cols):
|
|
|
|
| 748 |
st.session_state.train_ranges = {f:(float(tr_min[f]), float(tr_max[f])) for f in FEATURES}
|
| 749 |
st.markdown('<div class="st-message-box st-success">Case has been built and results are displayed below.</div>', unsafe_allow_html=True)
|
| 750 |
|
| 751 |
+
# --- Export (DEV phase) ---
|
| 752 |
+
default_sections = [s for s in _available_sections() if s.startswith("Training") or s.startswith("Testing")] + ["Training_Ranges","Info"]
|
| 753 |
+
render_export_button(phase_key="dev", default_sections=default_sections)
|
| 754 |
+
|
| 755 |
def _dev_block(df, m):
|
| 756 |
c1,c2,c3 = st.columns(3)
|
| 757 |
c1.metric("R", f"{m['R']:.2f}")
|
|
|
|
| 834 |
st.session_state.results["sv_val"]={"n":len(df), "pred_min":float(df[PRED_COL].min()), "pred_max":float(df[PRED_COL].max()), "oor":oor_pct}
|
| 835 |
st.session_state.results["oor_tbl"]=tbl
|
| 836 |
|
| 837 |
+
# --- Export (VALIDATE phase) ---
|
| 838 |
+
default_sections = ["Validation","Validation_Metrics","Validation_Summary"]
|
| 839 |
+
if st.session_state.results.get("oor_tbl") is not None and not st.session_state.results["oor_tbl"].empty:
|
| 840 |
+
default_sections.append("Validation_OOR")
|
| 841 |
+
default_sections += ["Training_Ranges","Info"]
|
| 842 |
+
render_export_button(phase_key="validate", default_sections=default_sections)
|
| 843 |
+
|
| 844 |
if "Validate" in st.session_state.results:
|
| 845 |
m = st.session_state.results["m_val"]
|
| 846 |
c1,c2,c3 = st.columns(3)
|
|
|
|
| 917 |
"oor":oor_pct
|
| 918 |
}
|
| 919 |
|
| 920 |
+
# --- Export (PREDICT phase) ---
|
| 921 |
+
default_sections = ["Prediction","Prediction_Summary","Training_Ranges","Info"]
|
| 922 |
+
render_export_button(phase_key="predict", default_sections=default_sections)
|
| 923 |
+
|
| 924 |
if "PredictOnly" in st.session_state.results:
|
| 925 |
df = st.session_state.results["PredictOnly"]; sv = st.session_state.results["sv_pred"]
|
| 926 |
|
|
|
|
| 981 |
df_centered_rounded(tbl)
|
| 982 |
st.session_state.show_preview_modal = False
|
| 983 |
|
| 984 |
+
# (Removed the old global bottom-of-page export — now shown per phase)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 985 |
|
| 986 |
# =========================
|
| 987 |
# Footer
|
|
|
|
| 993 |
© 2025 Smart Thinking AI-Solutions Team. All rights reserved.<br>
|
| 994 |
Website: <a href="https://smartthinking.com.sa" target="_blank" rel="noopener noreferrer">smartthinking.com.sa</a>
|
| 995 |
</div>
|
| 996 |
+
""", unsafe_allow_html=True)
|