feat: Remove deprecated body fat percentage chart and integrate master chart for report generation

- Deleted the old body fat percentage chart image.
- Updated report generation to load the new body fat percentage master chart for improved accuracy and consistency.
- Refactored context generation to reference the new chart in the report structure.
This commit is contained in:
bolade
2025-11-18 17:15:22 +01:00
parent 7e985c497e
commit 0090b7002c
6 changed files with 81 additions and 38 deletions

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

+39 -14
View File
@@ -295,7 +295,9 @@ class ContextGenerator:
# Calculate slope of VO2 Pulse
vo2_pulse_slope = self.pnoe_df["VO2 Pulse_smoothed"].diff()
window = max(1, len(self.pnoe_df) // 3) # Ensure window is at least 1
vo2_pulse_slope_smoothed = vo2_pulse_slope.rolling(window=window, min_periods=1).mean()
vo2_pulse_slope_smoothed = vo2_pulse_slope.rolling(
window=window, min_periods=1
).mean()
# Find where VO2 Pulse begins to drop (slope becomes negative)
mask_pulse = vo2_pulse_slope_smoothed <= 0
@@ -317,7 +319,10 @@ class ContextGenerator:
parts = zone_clean.split("-")
if len(parts) == 2:
try:
start, end = int(parts[0]), int(parts[1].replace("+", ""))
start, end = (
int(parts[0]),
int(parts[1].replace("+", "")),
)
if start <= vo2_pulse_drop_bpm <= end:
vo2_pulse_drop_zone = f"Zone {i}"
break
@@ -335,7 +340,9 @@ class ContextGenerator:
# Calculate slope of VO2 Breath
vo2_breath_slope = self.pnoe_df["VO2 Breath_smoothed"].diff()
vo2_breath_slope_smoothed = vo2_breath_slope.rolling(window=window, min_periods=1).mean()
vo2_breath_slope_smoothed = vo2_breath_slope.rolling(
window=window, min_periods=1
).mean()
# Find where VO2 Breath begins to drop
mask_breath = vo2_breath_slope_smoothed <= 0
@@ -357,7 +364,10 @@ class ContextGenerator:
parts = zone_clean.split("-")
if len(parts) == 2:
try:
start, end = int(parts[0]), int(parts[1].replace("+", ""))
start, end = (
int(parts[0]),
int(parts[1].replace("+", "")),
)
if start <= vo2_breath_drop_bpm <= end:
vo2_breath_drop_zone = f"Zone {i}"
break
@@ -393,7 +403,10 @@ class ContextGenerator:
# Find carbs and fat crossover point
crossover_idx = None
for idx in self.pnoe_df.index:
if self.pnoe_df.loc[idx, "CHO_smoothed"] > self.pnoe_df.loc[idx, "FAT_smoothed"]:
if (
self.pnoe_df.loc[idx, "CHO_smoothed"]
> self.pnoe_df.loc[idx, "FAT_smoothed"]
):
crossover_idx = idx
break
@@ -402,11 +415,15 @@ class ContextGenerator:
if crossover_idx is not None:
crossover_row = self.pnoe_df.loc[crossover_idx]
crossover_bpm = int(crossover_row["HR(bpm)_smoothed"])
crossover_heart_rate_pct = (crossover_bpm / max_hr * 100) if max_hr > 0 else 0
crossover_heart_rate_pct = (
(crossover_bpm / max_hr * 100) if max_hr > 0 else 0
)
# Get speed and incline at fat max
fat_max_speed = fat_max_row.get("Speed", 0)
fat_max_incline = fat_max_row.get("Incline", 2.0) if "Incline" in fat_max_row else 2.0
fat_max_incline = (
fat_max_row.get("Incline", 2.0) if "Incline" in fat_max_row else 2.0
)
return {
"fat_max_value": f"{fat_max_value:.2f}Kcals/min",
@@ -443,7 +460,9 @@ class ContextGenerator:
one_min_row = recovery_df[recovery_df["T(sec)"] <= one_min_time]
if len(one_min_row) > 0:
one_min_hr = one_min_row.iloc[-1]["HR(bpm)_smoothed"]
cardiac_recovery_pct = ((peak_hr - one_min_hr) / peak_hr * 100) if peak_hr > 0 else 0
cardiac_recovery_pct = (
((peak_hr - one_min_hr) / peak_hr * 100) if peak_hr > 0 else 0
)
else:
cardiac_recovery_pct = 33
@@ -453,7 +472,9 @@ class ContextGenerator:
two_min_row = recovery_df[recovery_df["T(sec)"] <= two_min_time]
if len(two_min_row) > 0:
two_min_vco2 = two_min_row.iloc[-1]["VCO2(ml/min)_smoothed"]
metabolic_recovery_pct = ((peak_vco2 - two_min_vco2) / peak_vco2 * 100) if peak_vco2 > 0 else 0
metabolic_recovery_pct = (
((peak_vco2 - two_min_vco2) / peak_vco2 * 100) if peak_vco2 > 0 else 0
)
else:
metabolic_recovery_pct = 65
@@ -463,7 +484,9 @@ class ContextGenerator:
two_five_min_row = recovery_df[recovery_df["T(sec)"] <= two_five_min_time]
if len(two_five_min_row) > 0:
two_five_min_bf = two_five_min_row.iloc[-1]["BF(bpm)_smoothed"]
breath_recovery_pct = ((peak_bf - two_five_min_bf) / peak_bf * 100) if peak_bf > 0 else 0
breath_recovery_pct = (
((peak_bf - two_five_min_bf) / peak_bf * 100) if peak_bf > 0 else 0
)
else:
breath_recovery_pct = 76
@@ -562,8 +585,8 @@ class ContextGenerator:
if "RER" in self.pnoe_df.columns and "FAT(%)" in self.pnoe_df.columns:
# Find rest phase with RER closest to 0.9
rest_phase = (
self.pnoe_df[self.pnoe_df["MET"] <= 1.1].copy()
if "MET" in self.pnoe_df.columns
self.pnoe_df[self.pnoe_df["RER"] == 0.9].copy()
if "RER" in self.pnoe_df.columns
else self.pnoe_df.copy()
)
if not rest_phase.empty:
@@ -736,11 +759,13 @@ class ContextGenerator:
"page_number": i + 12,
}
# Page 18 - Glossary with Body Fat Percentage Chart
# Page 18 - Glossary with Body Fat Percentage Master Chart
contexts["page_18"] = {
"patient_name": self.patient_info["name"],
"page_number": 18,
"body_fat_percentage_chart": graphs.get("body_fat_percent", ""),
"body_fat_percentage_chart": graphs.get(
"body_fat_percentage_master_chart", ""
),
}
# Page 19
+18
View File
@@ -404,6 +404,23 @@ class ReportGeneratorService:
print(f"Warning: Could not generate body fat percent chart: {e}")
graphs_dict["body_fat_percent"] = ""
# Load static body fat percentage master chart for page 18
master_chart_path = Path("app/body_fat_percentage_master_chart.png")
if master_chart_path.exists():
try:
with open(master_chart_path, "rb") as f:
graphs_dict["body_fat_percentage_master_chart"] = base64.b64encode(
f.read()
).decode("utf-8")
except Exception as e:
print(f"Warning: Could not load body fat percentage master chart: {e}")
graphs_dict["body_fat_percentage_master_chart"] = ""
else:
print(
f"Warning: Body fat percentage master chart not found at {master_chart_path}"
)
graphs_dict["body_fat_percentage_master_chart"] = ""
# Generate spirometry chart
print("Step 4: Generating spirometry chart...")
try:
@@ -419,6 +436,7 @@ class ReportGeneratorService:
print("Spirometry chart generated successfully")
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Warning: Could not generate spirometry chart: {e}")
print(f"Error details: {error_details}")