sammoftah's picture
Deploy Predictive Equipment Maintenance
31b3d22 verified
"""
Predictive Equipment Maintenance
Detect sensor anomalies and estimate maintenance urgency from time-series signals.
"""
from pathlib import Path
import numpy as np
import pandas as pd
import plotly.express as px
import streamlit as st
st.set_page_config(page_title="Predictive Equipment Maintenance", page_icon="⚙️", layout="wide")
def load_shared_css() -> None:
current_dir = Path(__file__).resolve().parent
candidates = [
current_dir / "shared" / "styles.css",
current_dir.parent / "shared" / "styles.css",
]
css_path = next(path for path in candidates if path.exists())
st.markdown(f"<style>{css_path.read_text(encoding='utf-8')}</style>", unsafe_allow_html=True)
load_shared_css()
def make_sensor_data(hours: int, drift: float, vibration_spike: float, seed: int = 7) -> pd.DataFrame:
rng = np.random.default_rng(seed)
t = np.arange(hours)
temperature = 68 + 0.035 * t * drift + 2.2 * np.sin(t / 13) + rng.normal(0, 0.7, hours)
vibration = 1.8 + 0.009 * t * drift + 0.4 * np.sin(t / 7) + rng.normal(0, 0.12, hours)
pressure = 38 - 0.015 * t * drift + rng.normal(0, 0.45, hours)
spike_start = max(0, int(hours * 0.72))
vibration[spike_start:] += vibration_spike
return pd.DataFrame({
"hour": t,
"temperature_c": temperature,
"vibration_mm_s": vibration,
"pressure_bar": pressure,
})
def add_anomaly_scores(df: pd.DataFrame) -> pd.DataFrame:
scored = df.copy()
for column in ["temperature_c", "vibration_mm_s", "pressure_bar"]:
rolling_mean = scored[column].rolling(24, min_periods=6).mean()
rolling_std = scored[column].rolling(24, min_periods=6).std().replace(0, np.nan)
scored[f"{column}_z"] = ((scored[column] - rolling_mean) / rolling_std).fillna(0).abs()
scored["anomaly_score"] = scored[["temperature_c_z", "vibration_mm_s_z", "pressure_bar_z"]].mean(axis=1)
scored["risk_band"] = pd.cut(
scored["anomaly_score"],
bins=[-0.1, 1.2, 2.0, 99],
labels=["Normal", "Watch", "Intervene"],
)
return scored
def maintenance_summary(scored: pd.DataFrame):
latest = scored.iloc[-1]
recent = scored.tail(24)
high_risk_hours = int((recent["risk_band"] == "Intervene").sum())
if latest["anomaly_score"] >= 2.0 or high_risk_hours >= 4:
action = "Schedule inspection within 24 hours."
elif latest["anomaly_score"] >= 1.2:
action = "Monitor closely and check lubrication, cooling, and mounting."
else:
action = "Continue normal operation and keep collecting telemetry."
remaining_useful_life = max(12, int(220 - latest["anomaly_score"] * 55 - high_risk_hours * 6))
return latest, high_risk_hours, remaining_useful_life, action
st.markdown("""
<div class="hero">
<div class="hf-badge">Time-Series ML</div>
<h1>⚙️ Predictive Equipment Maintenance</h1>
<p>Explore anomaly scoring, remaining useful life estimation, and maintenance triage on sensor streams.</p>
<div class="pill-row">
<span class="hf-chip">Rolling z-scores</span>
<span class="hf-chip">RUL estimate</span>
<span class="hf-chip">Telemetry dashboard</span>
</div>
</div>
""", unsafe_allow_html=True)
with st.sidebar:
st.markdown("### Sensor Scenario")
hours = st.slider("Hours of telemetry", 96, 720, 240, 24)
drift = st.slider("Wear drift", 0.0, 3.0, 1.2, 0.1)
vibration_spike = st.slider("Late vibration spike", 0.0, 4.0, 1.4, 0.1)
threshold = st.slider("Alert threshold", 0.8, 3.0, 2.0, 0.1)
df = make_sensor_data(hours, drift, vibration_spike)
scored = add_anomaly_scores(df)
latest, high_risk_hours, rul, action = maintenance_summary(scored)
metric_cols = st.columns(4)
metric_cols[0].metric("Current anomaly score", f"{latest['anomaly_score']:.2f}")
metric_cols[1].metric("Recent high-risk hours", high_risk_hours)
metric_cols[2].metric("Estimated RUL", f"{rul} hours")
metric_cols[3].metric("Risk band", str(latest["risk_band"]))
tab1, tab2, tab3 = st.tabs(["Telemetry", "Anomaly Score", "Decision Notes"])
with tab1:
long_df = scored.melt(
id_vars=["hour"],
value_vars=["temperature_c", "vibration_mm_s", "pressure_bar"],
var_name="sensor",
value_name="value",
)
fig = px.line(
long_df,
x="hour",
y="value",
color="sensor",
title="Synthetic equipment telemetry",
color_discrete_sequence=["#ffad7a", "#b8a9d9", "#7accff"],
)
st.plotly_chart(fig, use_container_width=True)
with tab2:
fig = px.line(scored, x="hour", y="anomaly_score", title="Rolling anomaly score")
fig.add_hline(y=threshold, line_dash="dash", line_color="#e8935c", annotation_text="alert threshold")
st.plotly_chart(fig, use_container_width=True)
st.dataframe(scored.tail(36), use_container_width=True, hide_index=True)
with tab3:
st.markdown(f"""
### Recommendation
**{action}**
This demo uses rolling z-score features so the math is inspectable. A production Hugging Face Space could replace the scorer with an Isolation Forest, Autoformer, TimesFM, or a fine-tuned time-series transformer, then publish benchmark telemetry as a Dataset.
""")