sighh
This commit is contained in:
@@ -27,7 +27,7 @@
|
|||||||
<img
|
<img
|
||||||
src="data:image/png;base64, {{ vo2_max_table }}"
|
src="data:image/png;base64, {{ vo2_max_table }}"
|
||||||
alt="VO2 Max Table"
|
alt="VO2 Max Table"
|
||||||
class="w-full max-w-4xl h-auto object-contain"
|
class="w-full max-w-4xl h-auto"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
<img
|
<img
|
||||||
src="data:image/png;base64, {{ hr_zones_table }}"
|
src="data:image/png;base64, {{ hr_zones_table }}"
|
||||||
alt="Heart Rate Zones Table"
|
alt="Heart Rate Zones Table"
|
||||||
class="w-full max-w-4xl h-auto object-contain"
|
class="w-full max-w-4xl h-auto"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -552,6 +552,407 @@ class ContextGenerator:
|
|||||||
"hr_athlete": hr_ranges["athlete"],
|
"hr_athlete": hr_ranges["athlete"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _calculate_rhr_table_data(self, age: int, gender: str) -> dict:
|
||||||
|
"""
|
||||||
|
Calculate Resting Heart Rate reference table data.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
age: Patient age
|
||||||
|
gender: Patient gender
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary containing age_range and ranges
|
||||||
|
"""
|
||||||
|
# Determine age range
|
||||||
|
if 18 <= age <= 25:
|
||||||
|
age_range = "18-25"
|
||||||
|
elif 26 <= age <= 35:
|
||||||
|
age_range = "26-35"
|
||||||
|
elif 36 <= age <= 45:
|
||||||
|
age_range = "36-45"
|
||||||
|
elif 46 <= age <= 55:
|
||||||
|
age_range = "46-55"
|
||||||
|
elif 56 <= age <= 65:
|
||||||
|
age_range = "56-65"
|
||||||
|
elif age > 65:
|
||||||
|
age_range = "65+"
|
||||||
|
else:
|
||||||
|
age_range = "18-25" # default for under 18
|
||||||
|
|
||||||
|
# RHR Master Chart
|
||||||
|
rhr_chart = {
|
||||||
|
"male": {
|
||||||
|
"18-25": {
|
||||||
|
"Poor": (85, None),
|
||||||
|
"Below Average": (79, 85),
|
||||||
|
"Average": (74, 79),
|
||||||
|
"Above Average": (70, 74),
|
||||||
|
"Good": (66, 70),
|
||||||
|
"Excellent": (61, 66),
|
||||||
|
"Athlete": (40, 61),
|
||||||
|
},
|
||||||
|
"26-35": {
|
||||||
|
"Poor": (83, None),
|
||||||
|
"Below Average": (77, 83),
|
||||||
|
"Average": (73, 77),
|
||||||
|
"Above Average": (69, 73),
|
||||||
|
"Good": (65, 69),
|
||||||
|
"Excellent": (60, 65),
|
||||||
|
"Athlete": (42, 60),
|
||||||
|
},
|
||||||
|
"36-45": {
|
||||||
|
"Poor": (85, None),
|
||||||
|
"Below Average": (79, 85),
|
||||||
|
"Average": (74, 79),
|
||||||
|
"Above Average": (70, 74),
|
||||||
|
"Good": (65, 70),
|
||||||
|
"Excellent": (60, 65),
|
||||||
|
"Athlete": (45, 60),
|
||||||
|
},
|
||||||
|
"46-55": {
|
||||||
|
"Poor": (84, None),
|
||||||
|
"Below Average": (78, 84),
|
||||||
|
"Average": (74, 78),
|
||||||
|
"Above Average": (70, 74),
|
||||||
|
"Good": (66, 70),
|
||||||
|
"Excellent": (61, 66),
|
||||||
|
"Athlete": (48, 61),
|
||||||
|
},
|
||||||
|
"56-65": {
|
||||||
|
"Poor": (84, None),
|
||||||
|
"Below Average": (78, 84),
|
||||||
|
"Average": (74, 78),
|
||||||
|
"Above Average": (70, 74),
|
||||||
|
"Good": (65, 70),
|
||||||
|
"Excellent": (60, 65),
|
||||||
|
"Athlete": (50, 60),
|
||||||
|
},
|
||||||
|
"65+": {
|
||||||
|
"Poor": (84, None),
|
||||||
|
"Below Average": (77, 84),
|
||||||
|
"Average": (73, 77),
|
||||||
|
"Above Average": (70, 73),
|
||||||
|
"Good": (65, 70),
|
||||||
|
"Excellent": (60, 65),
|
||||||
|
"Athlete": (52, 60),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"female": {
|
||||||
|
"18-25": {
|
||||||
|
"Poor": (82, None),
|
||||||
|
"Below Average": (74, 82),
|
||||||
|
"Average": (70, 74),
|
||||||
|
"Above Average": (66, 70),
|
||||||
|
"Good": (62, 66),
|
||||||
|
"Excellent": (56, 62),
|
||||||
|
"Athlete": (40, 56),
|
||||||
|
},
|
||||||
|
"26-35": {
|
||||||
|
"Poor": (82, None),
|
||||||
|
"Below Average": (75, 82),
|
||||||
|
"Average": (71, 75),
|
||||||
|
"Above Average": (66, 71),
|
||||||
|
"Good": (62, 66),
|
||||||
|
"Excellent": (55, 62),
|
||||||
|
"Athlete": (44, 55),
|
||||||
|
},
|
||||||
|
"36-45": {
|
||||||
|
"Poor": (83, None),
|
||||||
|
"Below Average": (76, 83),
|
||||||
|
"Average": (71, 76),
|
||||||
|
"Above Average": (67, 71),
|
||||||
|
"Good": (63, 67),
|
||||||
|
"Excellent": (57, 63),
|
||||||
|
"Athlete": (47, 57),
|
||||||
|
},
|
||||||
|
"46-55": {
|
||||||
|
"Poor": (84, None),
|
||||||
|
"Below Average": (77, 84),
|
||||||
|
"Average": (72, 77),
|
||||||
|
"Above Average": (68, 72),
|
||||||
|
"Good": (64, 68),
|
||||||
|
"Excellent": (58, 64),
|
||||||
|
"Athlete": (49, 58),
|
||||||
|
},
|
||||||
|
"56-65": {
|
||||||
|
"Poor": (82, None),
|
||||||
|
"Below Average": (76, 82),
|
||||||
|
"Average": (72, 76),
|
||||||
|
"Above Average": (68, 72),
|
||||||
|
"Good": (62, 68),
|
||||||
|
"Excellent": (57, 62),
|
||||||
|
"Athlete": (51, 57),
|
||||||
|
},
|
||||||
|
"65+": {
|
||||||
|
"Poor": (80, None),
|
||||||
|
"Below Average": (74, 80),
|
||||||
|
"Average": (70, 74),
|
||||||
|
"Above Average": (66, 70),
|
||||||
|
"Good": (62, 66),
|
||||||
|
"Excellent": (56, 62),
|
||||||
|
"Athlete": (52, 56),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
gender_key = "male" if gender.lower().startswith("m") else "female"
|
||||||
|
ranges = rhr_chart[gender_key][age_range]
|
||||||
|
|
||||||
|
# Format ranges
|
||||||
|
formatted_ranges = {}
|
||||||
|
for category, (min_val, max_val) in ranges.items():
|
||||||
|
if max_val is None:
|
||||||
|
formatted_ranges[category] = f"{min_val}bpm +"
|
||||||
|
else:
|
||||||
|
formatted_ranges[category] = f"{min_val}-{max_val}bpm"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"age_range": f"{age_range} ({gender[0].upper()})",
|
||||||
|
"ranges": formatted_ranges,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _calculate_zone_metrics(self, pnoe_metrics: Dict) -> Dict:
|
||||||
|
"""Calculate detailed metrics for each heart rate zone based on actual data"""
|
||||||
|
import math
|
||||||
|
|
||||||
|
# Get zone boundaries
|
||||||
|
fat_max_idx = self.pnoe_df["FAT_smoothed"].idxmax()
|
||||||
|
optimal_row = self.pnoe_df.loc[fat_max_idx]
|
||||||
|
|
||||||
|
# Detect VT1 and VT2
|
||||||
|
vt1 = pnoe_metrics.get("vt1")
|
||||||
|
vt2 = pnoe_metrics.get("vt2")
|
||||||
|
|
||||||
|
if not vt1 or not vt2:
|
||||||
|
# Return default values if thresholds not detected
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define zone boundaries (from notebook logic)
|
||||||
|
zone_1_start = math.floor(optimal_row["HR(bpm)_smoothed"] - 15)
|
||||||
|
zone_2_start = math.floor(optimal_row["HR(bpm)_smoothed"])
|
||||||
|
zone_3_start = math.floor(vt1["HeartRate"])
|
||||||
|
zone_4_start = math.floor(vt2["HeartRate"] - 10)
|
||||||
|
zone_5_start = math.floor(vt2["HeartRate"])
|
||||||
|
|
||||||
|
zone_1_end = zone_2_start
|
||||||
|
zone_2_end = math.floor(vt1["HeartRate"])
|
||||||
|
zone_3_end = zone_4_start
|
||||||
|
zone_4_end = zone_5_start
|
||||||
|
zone_5_end = math.floor(vt2["HeartRate"] + 10)
|
||||||
|
|
||||||
|
zones_list = [
|
||||||
|
("Zone 1", zone_1_start, zone_1_end),
|
||||||
|
("Zone 2", zone_2_start, zone_2_end),
|
||||||
|
("Zone 3", zone_3_start, zone_3_end),
|
||||||
|
("Zone 4", zone_4_start, zone_4_end),
|
||||||
|
("Zone 5", zone_5_start, zone_5_end),
|
||||||
|
]
|
||||||
|
|
||||||
|
ideal_breath_ranges = [
|
||||||
|
"Ideal Range: 15-20 breaths",
|
||||||
|
"Ideal Range: 20-25 breaths",
|
||||||
|
"Ideal Range: 25-30 breaths",
|
||||||
|
"Ideal Range: 30-35 breaths",
|
||||||
|
"Ideal Range: 40+ breaths",
|
||||||
|
]
|
||||||
|
|
||||||
|
def speed_to_pace(s_mph):
|
||||||
|
"""Convert speed in mph to pace in min/km"""
|
||||||
|
if s_mph <= 0:
|
||||||
|
return 0, 0
|
||||||
|
s_kmh = s_mph * 1.60934
|
||||||
|
p_min = 60 / s_kmh
|
||||||
|
p_m = int(p_min)
|
||||||
|
p_s = int((p_min % 1) * 60)
|
||||||
|
return p_m, p_s
|
||||||
|
|
||||||
|
zone_data = []
|
||||||
|
for i, (name, start, end) in enumerate(zones_list):
|
||||||
|
# Filter dataframe for the current zone
|
||||||
|
mask = (self.pnoe_df["HR(bpm)_smoothed"] >= start) & (
|
||||||
|
self.pnoe_df["HR(bpm)_smoothed"] <= end
|
||||||
|
)
|
||||||
|
zone_df = self.pnoe_df[mask]
|
||||||
|
|
||||||
|
if not zone_df.empty:
|
||||||
|
# Speed calculation
|
||||||
|
speed_series = zone_df[zone_df["Speed"] > 0.1]["Speed"]
|
||||||
|
if not speed_series.empty:
|
||||||
|
min_speed = speed_series.min()
|
||||||
|
max_speed = speed_series.max()
|
||||||
|
|
||||||
|
if abs(min_speed - max_speed) < 0.1:
|
||||||
|
speed_str = f"{min_speed:.1f}mph\n2% Incline"
|
||||||
|
else:
|
||||||
|
speed_str = f"{min_speed:.1f}-{max_speed:.1f}mph\n2% Incline"
|
||||||
|
|
||||||
|
# Pace calculation (max speed -> min pace, min speed -> max pace)
|
||||||
|
min_pace_m, min_pace_s = speed_to_pace(max_speed)
|
||||||
|
max_pace_m, max_pace_s = speed_to_pace(min_speed)
|
||||||
|
|
||||||
|
if min_pace_m == max_pace_m and min_pace_s == max_pace_s:
|
||||||
|
pace_str = f"{min_pace_m}:{min_pace_s:02d}min/km Pace"
|
||||||
|
else:
|
||||||
|
pace_str = (
|
||||||
|
f"{max_pace_m}:{max_pace_s:02d}-{min_pace_m}:{min_pace_s:02d}\n"
|
||||||
|
f"min/km Pace"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
speed_str = "-\n2% Incline"
|
||||||
|
pace_str = "-"
|
||||||
|
|
||||||
|
# Calories (using raw EE)
|
||||||
|
avg_cals = zone_df["EE(kcal/min)"].mean()
|
||||||
|
calories_str = f"Avg:\n{avg_cals:.1f}kcals/minute"
|
||||||
|
|
||||||
|
# Carb utilization (g/min)
|
||||||
|
avg_carbs_g = zone_df["CHO"].mean() / 4
|
||||||
|
carb_str = f"Avg: {avg_carbs_g:.1f}g/min\nCarb Utilization"
|
||||||
|
|
||||||
|
# Breathing
|
||||||
|
avg_breaths = zone_df["BF(bpm)_smoothed"].mean()
|
||||||
|
breath_str = (
|
||||||
|
f"Avg: {int(avg_breaths)} breaths\n{ideal_breath_ranges[i]}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
speed_str = "-\n2% Incline"
|
||||||
|
pace_str = "-"
|
||||||
|
calories_str = "-"
|
||||||
|
carb_str = "-"
|
||||||
|
breath_str = f"-\n{ideal_breath_ranges[i]}"
|
||||||
|
|
||||||
|
zone_data.append(
|
||||||
|
{
|
||||||
|
"zone_name": name,
|
||||||
|
"hr_bpm": f"{int(start)}-{int(end)}bpm",
|
||||||
|
"speed": speed_str,
|
||||||
|
"pace": pace_str,
|
||||||
|
"calories": calories_str,
|
||||||
|
"carb": carb_str,
|
||||||
|
"breathing": breath_str,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"zones": zone_data}
|
||||||
|
|
||||||
|
def _calculate_vo2_max_table_data(self, age: int, gender: str) -> Dict:
|
||||||
|
"""Calculate VO2 Max table data based on age and gender"""
|
||||||
|
# VO2 Max Master Chart Data (from notebook)
|
||||||
|
vo2_max_data = {
|
||||||
|
"20-29 (M)": {
|
||||||
|
"Very Poor": (None, 38.1),
|
||||||
|
"Poor": (38.1, 44.1),
|
||||||
|
"Fair": (44.1, 51.0),
|
||||||
|
"Good": (51.0, 56.9),
|
||||||
|
"Excellent": (56.9, 66.3),
|
||||||
|
"Superior": (66.3, None),
|
||||||
|
},
|
||||||
|
"30-39 (M)": {
|
||||||
|
"Very Poor": (None, 34.1),
|
||||||
|
"Poor": (34.1, 39.5),
|
||||||
|
"Fair": (39.5, 45.3),
|
||||||
|
"Good": (45.3, 51.3),
|
||||||
|
"Excellent": (51.3, 59.8),
|
||||||
|
"Superior": (59.8, None),
|
||||||
|
},
|
||||||
|
"40-49 (M)": {
|
||||||
|
"Very Poor": (None, 30.5),
|
||||||
|
"Poor": (30.5, 35.4),
|
||||||
|
"Fair": (35.4, 40.9),
|
||||||
|
"Good": (40.9, 46.3),
|
||||||
|
"Excellent": (46.3, 55.6),
|
||||||
|
"Superior": (55.6, None),
|
||||||
|
},
|
||||||
|
"50-59 (M)": {
|
||||||
|
"Very Poor": (None, 26.1),
|
||||||
|
"Poor": (26.1, 30.9),
|
||||||
|
"Fair": (30.9, 35.7),
|
||||||
|
"Good": (35.7, 40.9),
|
||||||
|
"Excellent": (40.9, 50.7),
|
||||||
|
"Superior": (50.7, None),
|
||||||
|
},
|
||||||
|
"60+ (M)": {
|
||||||
|
"Very Poor": (None, 22.4),
|
||||||
|
"Poor": (22.4, 26.5),
|
||||||
|
"Fair": (26.5, 32.2),
|
||||||
|
"Good": (32.2, 36.3),
|
||||||
|
"Excellent": (36.3, 43.0),
|
||||||
|
"Superior": (43.0, None),
|
||||||
|
},
|
||||||
|
"20-29 (F)": {
|
||||||
|
"Very Poor": (None, 28.6),
|
||||||
|
"Poor": (28.6, 33.7),
|
||||||
|
"Fair": (33.7, 38.5),
|
||||||
|
"Good": (38.5, 43.8),
|
||||||
|
"Excellent": (43.8, 56.0),
|
||||||
|
"Superior": (56.0, None),
|
||||||
|
},
|
||||||
|
"30-39 (F)": {
|
||||||
|
"Very Poor": (None, 24.1),
|
||||||
|
"Poor": (24.1, 28.2),
|
||||||
|
"Fair": (28.2, 32.2),
|
||||||
|
"Good": (32.2, 35.7),
|
||||||
|
"Excellent": (35.7, 45.8),
|
||||||
|
"Superior": (45.8, None),
|
||||||
|
},
|
||||||
|
"40-49 (F)": {
|
||||||
|
"Very Poor": (None, 22.7),
|
||||||
|
"Poor": (22.7, 26.5),
|
||||||
|
"Fair": (26.5, 30.5),
|
||||||
|
"Good": (30.5, 35.0),
|
||||||
|
"Excellent": (35.0, 42.3),
|
||||||
|
"Superior": (42.3, None),
|
||||||
|
},
|
||||||
|
"50-59 (F)": {
|
||||||
|
"Very Poor": (None, 21.5),
|
||||||
|
"Poor": (21.5, 24.9),
|
||||||
|
"Fair": (24.9, 28.7),
|
||||||
|
"Good": (28.7, 32.9),
|
||||||
|
"Excellent": (32.9, 40.4),
|
||||||
|
"Superior": (40.4, None),
|
||||||
|
},
|
||||||
|
"60+ (F)": {
|
||||||
|
"Very Poor": (None, 19.0),
|
||||||
|
"Poor": (19.0, 22.7),
|
||||||
|
"Fair": (22.7, 26.1),
|
||||||
|
"Good": (26.1, 30.1),
|
||||||
|
"Excellent": (30.1, 36.7),
|
||||||
|
"Superior": (36.7, None),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Determine age bracket
|
||||||
|
if age < 30:
|
||||||
|
age_key = "20-29"
|
||||||
|
elif age < 40:
|
||||||
|
age_key = "30-39"
|
||||||
|
elif age < 50:
|
||||||
|
age_key = "40-49"
|
||||||
|
elif age < 60:
|
||||||
|
age_key = "50-59"
|
||||||
|
else:
|
||||||
|
age_key = "60+"
|
||||||
|
|
||||||
|
gender_key = "(M)" if gender.lower() == "male" else "(F)"
|
||||||
|
key = f"{age_key} {gender_key}"
|
||||||
|
|
||||||
|
ranges = vo2_max_data.get(key, vo2_max_data["30-39 (F)"]) # Default
|
||||||
|
|
||||||
|
# Format the ranges for display
|
||||||
|
result = {}
|
||||||
|
for category, (min_val, max_val) in ranges.items():
|
||||||
|
if min_val is None:
|
||||||
|
result[category] = f"<{max_val:.1f}"
|
||||||
|
elif max_val is None:
|
||||||
|
result[category] = f"{min_val:.1f}+"
|
||||||
|
else:
|
||||||
|
result[category] = f"{min_val:.1f}-{max_val:.1f}"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"age_range": age_key,
|
||||||
|
"ranges": result,
|
||||||
|
}
|
||||||
|
|
||||||
def calculate_rmr_and_fuel_source(self) -> Dict:
|
def calculate_rmr_and_fuel_source(self) -> Dict:
|
||||||
"""Calculate RMR and fuel source from pnoe data"""
|
"""Calculate RMR and fuel source from pnoe data"""
|
||||||
metrics = {}
|
metrics = {}
|
||||||
@@ -722,9 +1123,16 @@ class ContextGenerator:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if graph_generator:
|
if graph_generator:
|
||||||
|
# Calculate VO2 Max table data
|
||||||
|
vo2_max_table_info = self._calculate_vo2_max_table_data(
|
||||||
|
self.patient_info["age"], self.patient_info["gender"]
|
||||||
|
)
|
||||||
|
|
||||||
# VO2 Max Table
|
# VO2 Max Table
|
||||||
vo2_max_columns = [
|
vo2_max_columns = [
|
||||||
"Age (F)",
|
"Age (F)"
|
||||||
|
if self.patient_info["gender"].lower() == "female"
|
||||||
|
else "Age (M)",
|
||||||
"Very Poor",
|
"Very Poor",
|
||||||
"Poor",
|
"Poor",
|
||||||
"Fair",
|
"Fair",
|
||||||
@@ -734,13 +1142,13 @@ class ContextGenerator:
|
|||||||
]
|
]
|
||||||
vo2_max_data = [
|
vo2_max_data = [
|
||||||
[
|
[
|
||||||
contexts["page_8"]["age_range"],
|
vo2_max_table_info["age_range"],
|
||||||
"19.0-24.1",
|
vo2_max_table_info["ranges"]["Very Poor"],
|
||||||
"24.1-28.2",
|
vo2_max_table_info["ranges"]["Poor"],
|
||||||
"28.2-32.2",
|
vo2_max_table_info["ranges"]["Fair"],
|
||||||
"32.2-35.7",
|
vo2_max_table_info["ranges"]["Good"],
|
||||||
"35.7-45.8",
|
vo2_max_table_info["ranges"]["Excellent"],
|
||||||
"45.8+",
|
vo2_max_table_info["ranges"]["Superior"],
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
vo2_max_colors = [
|
vo2_max_colors = [
|
||||||
@@ -763,6 +1171,10 @@ class ContextGenerator:
|
|||||||
save_as_base64=True,
|
save_as_base64=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Calculate zone metrics for the table
|
||||||
|
zone_metrics = self._calculate_zone_metrics(pnoe_metrics)
|
||||||
|
|
||||||
|
if zone_metrics.get("zones"):
|
||||||
# Heart Rate Zones Table
|
# Heart Rate Zones Table
|
||||||
hr_zones_columns = ["Zone 1", "Zone 2", "Zone 3", "Zone 4", "Zone 5"]
|
hr_zones_columns = ["Zone 1", "Zone 2", "Zone 3", "Zone 4", "Zone 5"]
|
||||||
hr_zones_data = [
|
hr_zones_data = [
|
||||||
@@ -780,48 +1192,12 @@ class ContextGenerator:
|
|||||||
"85-88% of Max Heart Rate",
|
"85-88% of Max Heart Rate",
|
||||||
"90% of Max Heart Rate",
|
"90% of Max Heart Rate",
|
||||||
],
|
],
|
||||||
[
|
[zone_metrics["zones"][i]["hr_bpm"] for i in range(5)],
|
||||||
pnoe_metrics.get("zone1_bpm", "81-96bpm"),
|
[zone_metrics["zones"][i]["speed"] for i in range(5)],
|
||||||
pnoe_metrics.get("zone2_bpm", "96-100bpm"),
|
[zone_metrics["zones"][i]["pace"] for i in range(5)],
|
||||||
pnoe_metrics.get("zone3_bpm", "100-178bpm"),
|
[zone_metrics["zones"][i]["calories"] for i in range(5)],
|
||||||
pnoe_metrics.get("zone4_bpm", "178-188bpm"),
|
[zone_metrics["zones"][i]["carb"] for i in range(5)],
|
||||||
pnoe_metrics.get("zone5_bpm", "188-198bpm"),
|
[zone_metrics["zones"][i]["breathing"] for i in range(5)],
|
||||||
],
|
|
||||||
[
|
|
||||||
"3.5mph\n2% Incline",
|
|
||||||
"3.5-4.0mph\n2% Incline",
|
|
||||||
"4.0-6.5mph\n2% Incline",
|
|
||||||
"6.5-7.0mph\n2% Incline",
|
|
||||||
"7.0-8.0mph\n2% Incline",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"10:39min/km Pace",
|
|
||||||
"10:39-9:19min/km Pace",
|
|
||||||
"9:19-5:44min/km Pace",
|
|
||||||
"5:44-5:20min/km Pace",
|
|
||||||
"5:20-4:40min/km Pace",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Avg:\n4.4kcals/minute",
|
|
||||||
"Avg:\n5.9kcals/minute",
|
|
||||||
"Avg:\n9.4kcals/minute",
|
|
||||||
"Avg:\n12.5kcals/minute",
|
|
||||||
"Avg:\n12.8kcals/minute",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Avg: 0.4g/min\nCarb Utilization",
|
|
||||||
"Avg: 0.6g/min\nCarb Utilization",
|
|
||||||
"Avg: 1.9g/min\nCarb Utilization",
|
|
||||||
"Avg: 2.9g/min\nCarb Utilization",
|
|
||||||
"Avg: 3.1g/min\nCarb Utilization",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Avg: 27 breaths\nIdeal: 15-20",
|
|
||||||
"Avg: 28 breaths\nIdeal: 20-25",
|
|
||||||
"Avg: 31 breaths\nIdeal: 25-30",
|
|
||||||
"Avg: 42 breaths\nIdeal: 30-35",
|
|
||||||
"Avg: 51 breaths\nIdeal: 40+",
|
|
||||||
],
|
|
||||||
]
|
]
|
||||||
hr_zones_colors = [
|
hr_zones_colors = [
|
||||||
["#ffffff"] * 5,
|
["#ffffff"] * 5,
|
||||||
@@ -834,13 +1210,15 @@ class ContextGenerator:
|
|||||||
["#ffcdd2", "#ffcdd2", "#fff9c4", "#c8e6c9", "#c8e6c9"],
|
["#ffcdd2", "#ffcdd2", "#fff9c4", "#c8e6c9", "#c8e6c9"],
|
||||||
]
|
]
|
||||||
|
|
||||||
contexts["page_8"]["hr_zones_table"] = graph_generator.generate_table_image(
|
contexts["page_8"]["hr_zones_table"] = (
|
||||||
|
graph_generator.generate_table_image(
|
||||||
data=hr_zones_data,
|
data=hr_zones_data,
|
||||||
columns=hr_zones_columns,
|
columns=hr_zones_columns,
|
||||||
cell_colors=hr_zones_colors,
|
cell_colors=hr_zones_colors,
|
||||||
header_color="#4dd0e1",
|
header_color="#4dd0e1",
|
||||||
save_as_base64=True,
|
save_as_base64=True,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Page 9
|
# Page 9
|
||||||
contexts["page_9"] = {
|
contexts["page_9"] = {
|
||||||
@@ -876,8 +1254,18 @@ class ContextGenerator:
|
|||||||
|
|
||||||
if graph_generator:
|
if graph_generator:
|
||||||
# Page 11 Resting Heart Rate Table
|
# Page 11 Resting Heart Rate Table
|
||||||
|
rhr_table_info = self._calculate_rhr_table_data(
|
||||||
|
self.patient_info["age"], self.patient_info["gender"]
|
||||||
|
)
|
||||||
|
|
||||||
|
gender_label = (
|
||||||
|
"Age (F)"
|
||||||
|
if self.patient_info["gender"].lower().startswith("f")
|
||||||
|
else "Age (M)"
|
||||||
|
)
|
||||||
|
|
||||||
rhr_columns = [
|
rhr_columns = [
|
||||||
"Age (F)",
|
gender_label,
|
||||||
"Poor",
|
"Poor",
|
||||||
"Below Average",
|
"Below Average",
|
||||||
"Average",
|
"Average",
|
||||||
@@ -888,14 +1276,14 @@ class ContextGenerator:
|
|||||||
]
|
]
|
||||||
rhr_data = [
|
rhr_data = [
|
||||||
[
|
[
|
||||||
contexts["page_11"]["hr_age_range"],
|
rhr_table_info["age_range"],
|
||||||
contexts["page_11"]["hr_poor"],
|
rhr_table_info["ranges"]["Poor"],
|
||||||
contexts["page_11"]["hr_below_avg"],
|
rhr_table_info["ranges"]["Below Average"],
|
||||||
contexts["page_11"]["hr_average"],
|
rhr_table_info["ranges"]["Average"],
|
||||||
contexts["page_11"]["hr_above_avg"],
|
rhr_table_info["ranges"]["Above Average"],
|
||||||
contexts["page_11"]["hr_good"],
|
rhr_table_info["ranges"]["Good"],
|
||||||
contexts["page_11"]["hr_excellent"],
|
rhr_table_info["ranges"]["Excellent"],
|
||||||
contexts["page_11"]["hr_athlete"],
|
rhr_table_info["ranges"]["Athlete"],
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
rhr_colors = [["#b2ebf2"] + ["#f5f5f5"] * 7]
|
rhr_colors = [["#b2ebf2"] + ["#f5f5f5"] * 7]
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Expected values from PDF (Page 5):
|
|||||||
import sys
|
import sys
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
sys.path.insert(0, '/Users/macbook/bio-performx')
|
sys.path.insert(0, '/home/oluwasanmi/Documents/Work/MKD/report_generation')
|
||||||
|
|
||||||
from app.services.context_generator import ContextGenerator
|
from app.services.context_generator import ContextGenerator
|
||||||
|
|
||||||
@@ -41,8 +41,8 @@ PATIENT_DATA = {
|
|||||||
USE_PDF_RMR = True # Set to True to use PDF's measured RMR instead of calculating from CSV
|
USE_PDF_RMR = True # Set to True to use PDF's measured RMR instead of calculating from CSV
|
||||||
|
|
||||||
# File paths
|
# File paths
|
||||||
PNOE_FILE = "Pnoe_20250729_1550-Moran_Keirstyn (2).csv"
|
PNOE_FILE = "/home/oluwasanmi/Documents/Work/MKD/report_generation/data/Pnoe_20250729_1550-Moran_Keirstyn.csv"
|
||||||
SPIROMETRY_FILE = "data/extracted_spirometry_table.csv"
|
SPIROMETRY_FILE = "/home/oluwasanmi/Documents/Work/MKD/report_generation/data/spirometry_data.csv"
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
print("=" * 80)
|
print("=" * 80)
|
||||||
|
|||||||
Reference in New Issue
Block a user