Spaces:
Runtime error
Runtime error
Upload folder using huggingface_hub
Browse files- backend/main.py +7 -3
- frontend/pages/2_Data_Analytics.py +100 -52
backend/main.py
CHANGED
|
@@ -103,9 +103,13 @@ def history():
|
|
| 103 |
async def get_dataset():
|
| 104 |
data_path = '/app/data/patient_dataset.csv' if os.path.exists('/app/data') else '../data/patient_dataset.csv' if os.path.exists('../data') else 'data/patient_dataset.csv'
|
| 105 |
if os.path.exists(data_path):
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
return {"error": "Dataset not found"}
|
| 110 |
|
| 111 |
@app.get("/sync")
|
|
|
|
| 103 |
async def get_dataset():
|
| 104 |
data_path = '/app/data/patient_dataset.csv' if os.path.exists('/app/data') else '../data/patient_dataset.csv' if os.path.exists('../data') else 'data/patient_dataset.csv'
|
| 105 |
if os.path.exists(data_path):
|
| 106 |
+
try:
|
| 107 |
+
# Force UTF-8 and strip column whitespace
|
| 108 |
+
df = pd.read_csv(data_path, encoding='utf-8')
|
| 109 |
+
df.columns = [c.strip() for c in df.columns]
|
| 110 |
+
return df.to_dict(orient="records")
|
| 111 |
+
except Exception as e:
|
| 112 |
+
return {"error": f"Failed to read dataset: {str(e)}"}
|
| 113 |
return {"error": "Dataset not found"}
|
| 114 |
|
| 115 |
@app.get("/sync")
|
frontend/pages/2_Data_Analytics.py
CHANGED
|
@@ -20,7 +20,12 @@ def load_data():
|
|
| 20 |
if resp.status_code == 200:
|
| 21 |
data = resp.json()
|
| 22 |
if isinstance(data, list):
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
st.error(f"Backend Error: {data.get('error', 'Unknown')}")
|
| 25 |
except Exception as e:
|
| 26 |
st.error(f"Failed to connect to backend: {e}")
|
|
@@ -42,14 +47,34 @@ else:
|
|
| 42 |
df = full_df
|
| 43 |
st.success(f"Loaded **{len(full_df):,}** patient records from backend.")
|
| 44 |
|
| 45 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
st.markdown("### 🫀 Vital Signs Distribution by Condition")
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
selected_vital = st.selectbox("Select Vital Sign", vitals)
|
| 49 |
|
| 50 |
fig_box = px.box(
|
| 51 |
-
df, x=
|
| 52 |
-
color=
|
| 53 |
color_discrete_map=DISEASE_COLORS,
|
| 54 |
title=f"{selected_vital} Distribution Across Conditions",
|
| 55 |
template="plotly_dark",
|
|
@@ -67,44 +92,56 @@ else:
|
|
| 67 |
c1, c2 = st.columns(2)
|
| 68 |
with c1:
|
| 69 |
st.markdown("##### Heart Rate vs SpO2 (Scatter Matrix)")
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
with c2:
|
| 87 |
st.markdown("##### Temperature by Condition (Violin Plot)")
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
st.markdown("---")
|
| 103 |
|
| 104 |
c3, c4 = st.columns(2)
|
| 105 |
with c3:
|
| 106 |
st.markdown("##### 🏥 Disease Distribution (Population Count)")
|
| 107 |
-
counts = df[
|
| 108 |
counts.columns = ["Condition", "Patient Count"]
|
| 109 |
fig_bar = px.bar(
|
| 110 |
counts, x="Condition", y="Patient Count",
|
|
@@ -123,33 +160,44 @@ else:
|
|
| 123 |
|
| 124 |
with c4:
|
| 125 |
st.markdown("##### 🩸 Blood Pressure Density (Systolic vs Diastolic)")
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
|
| 139 |
st.markdown("---")
|
| 140 |
st.markdown("### 📈 Patient-Specific Real-Time Monitoring")
|
| 141 |
-
|
|
|
|
|
|
|
|
|
|
| 142 |
if p_id:
|
| 143 |
-
patient_data = df[df[
|
| 144 |
-
disease = patient_data[
|
| 145 |
d_color = DISEASE_COLORS.get(disease, "#636efa")
|
| 146 |
st.markdown(f"**Patient Condition:** <span style='color:{d_color}; font-weight:bold'>{disease}</span>", unsafe_allow_html=True)
|
| 147 |
|
| 148 |
periods = 60
|
| 149 |
dates = pd.date_range(end=pd.Timestamp.now(), periods=periods, freq='25s')
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
|
|
|
|
|
|
| 153 |
sim_hr = base_hr + np.random.normal(0, 1.2, periods).cumsum()
|
| 154 |
sim_spo2 = np.clip(base_spo2 + np.random.normal(0, 0.3, periods).cumsum(), 85, 100)
|
| 155 |
sim_temp = base_temp + np.random.normal(0, 0.05, periods).cumsum()
|
|
|
|
| 20 |
if resp.status_code == 200:
|
| 21 |
data = resp.json()
|
| 22 |
if isinstance(data, list):
|
| 23 |
+
df = pd.DataFrame(data)
|
| 24 |
+
# Normalize column names: strip whitespace and ensure common naming
|
| 25 |
+
df.columns = [c.strip() for c in df.columns]
|
| 26 |
+
# Fix common encoding issues for temperature
|
| 27 |
+
df.columns = [c.replace('°C', '°C') for c in df.columns]
|
| 28 |
+
return df
|
| 29 |
st.error(f"Backend Error: {data.get('error', 'Unknown')}")
|
| 30 |
except Exception as e:
|
| 31 |
st.error(f"Failed to connect to backend: {e}")
|
|
|
|
| 47 |
df = full_df
|
| 48 |
st.success(f"Loaded **{len(full_df):,}** patient records from backend.")
|
| 49 |
|
| 50 |
+
# ── Safety Check for Column Names ──
|
| 51 |
+
available_cols = df.columns.tolist()
|
| 52 |
+
target_x = "Predicted Disease"
|
| 53 |
+
|
| 54 |
+
if target_x not in available_cols:
|
| 55 |
+
# Fallback to similar name if exact match fails
|
| 56 |
+
matches = [c for c in available_cols if "Disease" in c or "Condition" in c]
|
| 57 |
+
if matches:
|
| 58 |
+
target_x = matches[0]
|
| 59 |
+
else:
|
| 60 |
+
st.error(f"Required column '{target_x}' missing from dataset. Found: {available_cols}")
|
| 61 |
+
st.stop()
|
| 62 |
+
|
| 63 |
st.markdown("### 🫀 Vital Signs Distribution by Condition")
|
| 64 |
+
|
| 65 |
+
# Define possible vitals and filter by what's actually in the DF
|
| 66 |
+
vitals_all = ["Heart Rate (bpm)", "SpO2 Level (%)", "Systolic Blood Pressure (mmHg)", "Diastolic Blood Pressure (mmHg)", "Body Temperature (°C)"]
|
| 67 |
+
vitals = [v for v in vitals_all if v in available_cols]
|
| 68 |
+
|
| 69 |
+
if not vitals:
|
| 70 |
+
st.error(f"No clinical vital sign columns found in dataset. Found: {available_cols}")
|
| 71 |
+
st.stop()
|
| 72 |
+
|
| 73 |
selected_vital = st.selectbox("Select Vital Sign", vitals)
|
| 74 |
|
| 75 |
fig_box = px.box(
|
| 76 |
+
df, x=target_x, y=selected_vital,
|
| 77 |
+
color=target_x,
|
| 78 |
color_discrete_map=DISEASE_COLORS,
|
| 79 |
title=f"{selected_vital} Distribution Across Conditions",
|
| 80 |
template="plotly_dark",
|
|
|
|
| 92 |
c1, c2 = st.columns(2)
|
| 93 |
with c1:
|
| 94 |
st.markdown("##### Heart Rate vs SpO2 (Scatter Matrix)")
|
| 95 |
+
# Safety check for scatter columns
|
| 96 |
+
x_scatter = "Heart Rate (bpm)" if "Heart Rate (bpm)" in available_cols else None
|
| 97 |
+
y_scatter = "SpO2 Level (%)" if "SpO2 Level (%)" in available_cols else None
|
| 98 |
+
|
| 99 |
+
if x_scatter and y_scatter:
|
| 100 |
+
fig_scatter = px.scatter(
|
| 101 |
+
df, x=x_scatter, y=y_scatter,
|
| 102 |
+
color=target_x,
|
| 103 |
+
color_discrete_map=DISEASE_COLORS,
|
| 104 |
+
opacity=0.4, size_max=6,
|
| 105 |
+
title="Heart Rate vs SpO2 Levels",
|
| 106 |
+
template="plotly_dark",
|
| 107 |
+
marginal_x="histogram",
|
| 108 |
+
marginal_y="histogram"
|
| 109 |
+
)
|
| 110 |
+
fig_scatter.update_layout(
|
| 111 |
+
paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)',
|
| 112 |
+
height=450, legend=dict(orientation="h", yanchor="bottom", y=-0.35)
|
| 113 |
+
)
|
| 114 |
+
st.plotly_chart(fig_scatter, use_container_width=True)
|
| 115 |
+
else:
|
| 116 |
+
st.warning("Scatter plot unavailable: Required columns missing.")
|
| 117 |
|
| 118 |
with c2:
|
| 119 |
st.markdown("##### Temperature by Condition (Violin Plot)")
|
| 120 |
+
y_violin = "Body Temperature (°C)" if "Body Temperature (°C)" in available_cols else None
|
| 121 |
+
|
| 122 |
+
if y_violin:
|
| 123 |
+
fig_violin = px.violin(
|
| 124 |
+
df, x=target_x, y=y_violin,
|
| 125 |
+
color=target_x,
|
| 126 |
+
color_discrete_map=DISEASE_COLORS,
|
| 127 |
+
box=True, points=False,
|
| 128 |
+
title="Body Temperature Distribution",
|
| 129 |
+
template="plotly_dark"
|
| 130 |
+
)
|
| 131 |
+
fig_violin.update_layout(
|
| 132 |
+
paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)',
|
| 133 |
+
height=450, showlegend=False, xaxis_title="Condition", yaxis_title="Temperature (°C)"
|
| 134 |
+
)
|
| 135 |
+
st.plotly_chart(fig_violin, use_container_width=True)
|
| 136 |
+
else:
|
| 137 |
+
st.warning("Violin plot unavailable: Temperature column missing.")
|
| 138 |
|
| 139 |
st.markdown("---")
|
| 140 |
|
| 141 |
c3, c4 = st.columns(2)
|
| 142 |
with c3:
|
| 143 |
st.markdown("##### 🏥 Disease Distribution (Population Count)")
|
| 144 |
+
counts = df[target_x].value_counts().reset_index()
|
| 145 |
counts.columns = ["Condition", "Patient Count"]
|
| 146 |
fig_bar = px.bar(
|
| 147 |
counts, x="Condition", y="Patient Count",
|
|
|
|
| 160 |
|
| 161 |
with c4:
|
| 162 |
st.markdown("##### 🩸 Blood Pressure Density (Systolic vs Diastolic)")
|
| 163 |
+
x_heat = "Systolic Blood Pressure (mmHg)" if "Systolic Blood Pressure (mmHg)" in available_cols else None
|
| 164 |
+
y_heat = "Diastolic Blood Pressure (mmHg)" if "Diastolic Blood Pressure (mmHg)" in available_cols else None
|
| 165 |
+
|
| 166 |
+
if x_heat and y_heat:
|
| 167 |
+
fig_density = px.density_heatmap(
|
| 168 |
+
df, x=x_heat, y=y_heat,
|
| 169 |
+
color_continuous_scale="Turbo",
|
| 170 |
+
title="Blood Pressure Population Heatmap",
|
| 171 |
+
template="plotly_dark",
|
| 172 |
+
nbinsx=30, nbinsy=30
|
| 173 |
+
)
|
| 174 |
+
fig_density.update_layout(
|
| 175 |
+
paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)',
|
| 176 |
+
height=420
|
| 177 |
+
)
|
| 178 |
+
st.plotly_chart(fig_density, use_container_width=True)
|
| 179 |
+
else:
|
| 180 |
+
st.warning("Density heatmap unavailable: Blood Pressure columns missing.")
|
| 181 |
|
| 182 |
st.markdown("---")
|
| 183 |
st.markdown("### 📈 Patient-Specific Real-Time Monitoring")
|
| 184 |
+
# Use Patient Number if available
|
| 185 |
+
id_col = "Patient Number" if "Patient Number" in available_cols else available_cols[0]
|
| 186 |
+
p_id = st.selectbox("Select Patient ID to view live telemetry:", df[id_col].unique()[:50])
|
| 187 |
+
|
| 188 |
if p_id:
|
| 189 |
+
patient_data = df[df[id_col] == p_id].iloc[0]
|
| 190 |
+
disease = patient_data[target_x]
|
| 191 |
d_color = DISEASE_COLORS.get(disease, "#636efa")
|
| 192 |
st.markdown(f"**Patient Condition:** <span style='color:{d_color}; font-weight:bold'>{disease}</span>", unsafe_allow_html=True)
|
| 193 |
|
| 194 |
periods = 60
|
| 195 |
dates = pd.date_range(end=pd.Timestamp.now(), periods=periods, freq='25s')
|
| 196 |
+
|
| 197 |
+
# Safe access for real-time emulation
|
| 198 |
+
base_hr = patient_data.get('Heart Rate (bpm)', 75)
|
| 199 |
+
base_spo2 = patient_data.get('SpO2 Level (%)', 98)
|
| 200 |
+
base_temp = patient_data.get('Body Temperature (°C)', 37.0)
|
| 201 |
sim_hr = base_hr + np.random.normal(0, 1.2, periods).cumsum()
|
| 202 |
sim_spo2 = np.clip(base_spo2 + np.random.normal(0, 0.3, periods).cumsum(), 85, 100)
|
| 203 |
sim_temp = base_temp + np.random.normal(0, 0.05, periods).cumsum()
|