Checkpoint 3

This commit is contained in:
bolade
2025-11-28 16:19:32 +01:00
parent fc62b64624
commit 35ea522283
10 changed files with 113 additions and 72 deletions
Binary file not shown.
Binary file not shown.
+3 -3
View File
@@ -179,10 +179,10 @@ async def upload_files(
# Prepare patient information # Prepare patient information
patient_name = f"{first_name} {last_name}" patient_name = f"{first_name} {last_name}"
print(f"DEBUG: Received next_testing_date: '{next_testing_date}'") print(f"DEBUG: Received next_testing_date: '{next_testing_date}'")
# Generate session_id internally using timestamp for unique identification # Generate session_id internally using timestamp for unique identification
session_id = datetime.now().strftime("%Y%m%d_%H%M%S") session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
patient_info = { patient_info = {
"patient_name": patient_name, "patient_name": patient_name,
"first_name": first_name, "first_name": first_name,
@@ -642,7 +642,7 @@ async def generate_report(
# Generate session_id internally using timestamp for unique identification # Generate session_id internally using timestamp for unique identification
session_id = datetime.now().strftime("%Y%m%d_%H%M%S") session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
# Prepare patient information # Prepare patient information
patient_info = { patient_info = {
"patient_name": patient_name, "patient_name": patient_name,
+14 -7
View File
@@ -15,7 +15,8 @@
<!-- Lung Analysis --> <!-- Lung Analysis -->
<div class="flex items-start bg-gray-200 h-24"> <div class="flex items-start bg-gray-200 h-24">
<div <div
class="bg-black text-white text-2xl font-bold w-16 h-full flex items-center justify-center mr-8 flex-shrink-0" class="bg-black text-white text-4xl font-extrabold w-24 h-24 flex items-center justify-center mr-8 flex-shrink-0"
style="border-radius: 0;"
> >
3 3
</div> </div>
@@ -35,7 +36,8 @@
<!-- Cardio Metrics --> <!-- Cardio Metrics -->
<div class="flex items-start bg-gray-200 h-24"> <div class="flex items-start bg-gray-200 h-24">
<div <div
class="bg-black text-white text-2xl font-bold w-16 h-full flex items-center justify-center mr-8 flex-shrink-0" class="bg-black text-white text-4xl font-extrabold w-24 h-24 flex items-center justify-center mr-8 flex-shrink-0"
style="border-radius: 0;"
> >
4 4
</div> </div>
@@ -52,7 +54,8 @@
<!-- Fuel Utilization --> <!-- Fuel Utilization -->
<div class="flex items-start bg-gray-200 h-24"> <div class="flex items-start bg-gray-200 h-24">
<div <div
class="bg-black text-white text-2xl font-bold w-16 h-full flex items-center justify-center mr-8 flex-shrink-0" class="bg-black text-white text-4xl font-extrabold w-24 h-24 flex items-center justify-center mr-8 flex-shrink-0"
style="border-radius: 0;"
> >
5 5
</div> </div>
@@ -66,7 +69,8 @@
<!-- Local Muscle Activity --> <!-- Local Muscle Activity -->
<div class="flex items-start bg-gray-200 h-24"> <div class="flex items-start bg-gray-200 h-24">
<div <div
class="bg-black text-white text-2xl font-bold w-16 h-full flex items-center justify-center mr-8 flex-shrink-0" class="bg-black text-white text-4xl font-extrabold w-24 h-24 flex items-center justify-center mr-8 flex-shrink-0"
style="border-radius: 0;"
> >
9 9
</div> </div>
@@ -80,7 +84,8 @@
<!-- Training Recommendations --> <!-- Training Recommendations -->
<div class="flex items-start bg-gray-200 h-24"> <div class="flex items-start bg-gray-200 h-24">
<div <div
class="bg-black text-white text-2xl font-bold w-16 h-full flex items-center justify-center mr-8 flex-shrink-0" class="bg-black text-white text-4xl font-extrabold w-24 h-24 flex items-center justify-center mr-8 flex-shrink-0"
style="border-radius: 0;"
> >
10 10
</div> </div>
@@ -94,7 +99,8 @@
<!-- Next Steps --> <!-- Next Steps -->
<div class="flex items-start bg-gray-200 h-24"> <div class="flex items-start bg-gray-200 h-24">
<div <div
class="bg-black text-white text-2xl font-bold w-16 h-full flex items-center justify-center mr-8 flex-shrink-0" class="bg-black text-white text-4xl font-extrabold w-24 h-24 flex items-center justify-center mr-8 flex-shrink-0"
style="border-radius: 0;"
> >
12 12
</div> </div>
@@ -111,7 +117,8 @@
<!-- Glossary --> <!-- Glossary -->
<div class="flex items-start bg-gray-200 h-24"> <div class="flex items-start bg-gray-200 h-24">
<div <div
class="bg-black text-white text-2xl font-bold w-16 h-full flex items-center justify-center mr-8 flex-shrink-0" class="bg-black text-white text-4xl font-extrabold w-24 h-24 flex items-center justify-center mr-8 flex-shrink-0"
style="border-radius: 0;"
> >
13 13
</div> </div>
+8 -9
View File
@@ -15,7 +15,8 @@
<!-- Nutrition Guidelines --> <!-- Nutrition Guidelines -->
<div class="flex items-start bg-gray-200 h-24"> <div class="flex items-start bg-gray-200 h-24">
<div <div
class="bg-black text-white text-2xl font-bold w-16 h-full flex items-center justify-center mr-8 flex-shrink-0" class="bg-black text-white text-4xl font-extrabold w-24 h-24 flex items-center justify-center mr-8 flex-shrink-0"
style="border-radius: 0;"
> >
3 3
</div> </div>
@@ -35,7 +36,8 @@
<!-- Nutrition Recommendations --> <!-- Nutrition Recommendations -->
<div class="flex items-start bg-gray-200 h-24"> <div class="flex items-start bg-gray-200 h-24">
<div <div
class="bg-black text-white text-2xl font-bold w-16 h-full flex items-center justify-center mr-8 flex-shrink-0" class="bg-black text-white text-4xl font-extrabold w-24 h-24 flex items-center justify-center mr-8 flex-shrink-0"
style="border-radius: 0;"
> >
4 4
</div> </div>
@@ -49,7 +51,8 @@
<!-- Next Steps --> <!-- Next Steps -->
<div class="flex items-start bg-gray-200 h-24"> <div class="flex items-start bg-gray-200 h-24">
<div <div
class="bg-black text-white text-2xl font-bold w-16 h-full flex items-center justify-center mr-8 flex-shrink-0" class="bg-black text-white text-4xl font-extrabold w-24 h-24 flex items-center justify-center mr-8 flex-shrink-0"
style="border-radius: 0;"
> >
5 5
</div> </div>
@@ -66,7 +69,8 @@
<!-- Glossary --> <!-- Glossary -->
<div class="flex items-start bg-gray-200 h-24"> <div class="flex items-start bg-gray-200 h-24">
<div <div
class="bg-black text-white text-2xl font-bold w-16 h-full flex items-center justify-center mr-8 flex-shrink-0" class="bg-black text-white text-4xl font-extrabold w-24 h-24 flex items-center justify-center mr-8 flex-shrink-0"
style="border-radius: 0;"
> >
6 6
</div> </div>
@@ -82,8 +86,3 @@
</div> </div>
</div> </div>
</div> </div>
+1 -1
View File
@@ -26,7 +26,7 @@
<!-- Indications Box --> <!-- Indications Box -->
<div class="bg-gray-200 rounded-lg p-4 text-center mb-2"> <div class="bg-gray-200 rounded-lg p-4 text-center mb-2">
<h3 class="font-semibold text-lg mb-2">Indications</h3> <h3 class="font-semibold text-lg mb-2">Indications</h3>
<p >{{ indication | default('No Respiratory Capacity Limitations')}}</p> <p >{{ indication | default('No Respiratory Capacity Limitation')}}</p>
</div> </div>
</div> </div>
+64 -32
View File
@@ -232,10 +232,15 @@ class ContextGenerator:
if zone_key in metric_overrides: if zone_key in metric_overrides:
metrics[zone_key] = metric_overrides[zone_key] metrics[zone_key] = metric_overrides[zone_key]
else: else:
fat_max_idx = self.pnoe_df["FAT_smoothed"].idxmax() # Use optimal fat burning zone (highest fat:carb ratio) - same as _calculate_zone_metrics
fat_max_row = self.pnoe_df.loc[fat_max_idx] # This ensures consistency between zone calculations and zone metrics
self.pnoe_df["fat_carb_ratio"] = self.pnoe_df["FAT_smoothed"] / (
self.pnoe_df["CHO_smoothed"] + 0.00000001
)
optimal_fat_idx = self.pnoe_df["fat_carb_ratio"].idxmax()
optimal_row = self.pnoe_df.loc[optimal_fat_idx]
zones = self._calculate_hr_zones( zones = self._calculate_hr_zones(
metrics["vt1"], metrics["vt2"], fat_max_row metrics["vt1"], metrics["vt2"], optimal_row
) )
metrics.update(zones) metrics.update(zones)
@@ -280,29 +285,46 @@ class ContextGenerator:
return vt1, vt2 return vt1, vt2
def _calculate_hr_zones( def _calculate_hr_zones(
self, vt1: Optional[Dict], vt2: Optional[Dict], fat_max_row: pd.Series self, vt1: Optional[Dict], vt2: Optional[Dict], optimal_row: pd.Series
) -> Dict: ) -> Dict:
"""Calculate heart rate zones based on thresholds""" """Calculate heart rate zones based on thresholds
Uses optimal fat burning zone (highest fat:carb ratio) to match _calculate_zone_metrics.
This ensures consistency between zone string calculations and zone metrics table.
"""
import math
zones = {} zones = {}
if vt1 and vt2: if vt1 and vt2:
zone_1_start = fat_max_row["HR(bpm)_smoothed"] - 15 # Use same zone boundary calculation as _calculate_zone_metrics
zone_2_start = fat_max_row["HR(bpm)_smoothed"] zone_1_start = math.floor(optimal_row["HR(bpm)_smoothed"] - 15)
zone_3_start = vt1["HeartRate"] zone_2_start = math.floor(optimal_row["HR(bpm)_smoothed"])
zone_4_start = vt2["HeartRate"] - 10 zone_3_start = math.floor(vt1["HeartRate"])
zone_5_start = vt2["HeartRate"] + 10 zone_4_start = math.floor(vt2["HeartRate"] - 10)
zone_5_start = math.floor(vt2["HeartRate"])
# zone_5_end is calculated for consistency with _calculate_zone_metrics
# (not used in string format since zone 5 is open-ended: "+bpm")
zone_5_end = math.floor(vt2["HeartRate"] + 10) # noqa: F841
zones["zone1_bpm"] = f"{int(zone_1_start)}-{int(zone_2_start)}bpm" # Calculate zone ends to match _calculate_zone_metrics exactly
zones["zone2_bpm"] = f"{int(zone_2_start)}-{int(vt1['HeartRate'])}bpm" zone_1_end = zone_2_start
zones["zone3_bpm"] = f"{int(zone_3_start)}-{int(zone_4_start)}bpm" zone_2_end = math.floor(vt1["HeartRate"])
zones["zone4_bpm"] = f"{int(zone_4_start)}-{int(zone_5_start)}bpm" zone_3_end = zone_4_start
zones["zone5_bpm"] = f"{int(zone_5_start)}+bpm" zone_4_end = zone_5_start
# Format zones to match _calculate_zone_metrics output
zones["zone1_bpm"] = f"{int(zone_1_start)}-{int(zone_1_end)}bpm"
zones["zone2_bpm"] = f"{int(zone_2_start)}-{int(zone_2_end)}bpm"
zones["zone3_bpm"] = f"{int(zone_3_start)}-{int(zone_3_end)}bpm"
zones["zone4_bpm"] = f"{int(zone_4_start)}-{int(zone_4_end)}bpm"
zones["zone5_bpm"] = f"{int(zone_5_start)}-{int(zone_5_end)}bpm"
else: else:
max_hr = 220 - self.patient_info["age"] max_hr = 220 - self.patient_info["age"]
zones["zone1_bpm"] = f"{int(max_hr * 0.55)}-{int(max_hr * 0.65)}bpm" zones["zone1_bpm"] = f"{int(max_hr * 0.55)}-{int(max_hr * 0.65)}bpm"
zones["zone2_bpm"] = f"{int(max_hr * 0.65)}-{int(max_hr * 0.75)}bpm" zones["zone2_bpm"] = f"{int(max_hr * 0.65)}-{int(max_hr * 0.75)}bpm"
zones["zone3_bpm"] = f"{int(max_hr * 0.75)}-{int(max_hr * 0.85)}bpm" zones["zone3_bpm"] = f"{int(max_hr * 0.75)}-{int(max_hr * 0.85)}bpm"
zones["zone4_bpm"] = f"{int(max_hr * 0.85)}-{int(max_hr * 0.95)}bpm" zones["zone4_bpm"] = f"{int(max_hr * 0.85)}-{int(max_hr * 0.95)}bpm"
zones["zone5_bpm"] = f"{int(max_hr * 0.95)}+bpm" zones["zone5_bpm"] = f"{int(max_hr * 0.95)}-{int(max_hr * 1.05)}bpm"
return zones return zones
def _calculate_vo2_drop_points(self, pnoe_metrics: Dict) -> Dict: def _calculate_vo2_drop_points(self, pnoe_metrics: Dict) -> Dict:
@@ -1180,7 +1202,9 @@ class ContextGenerator:
"page_number": 4, "page_number": 4,
"fat_percentage": f"{self.patient_info['fat_percentage']:.1f}", "fat_percentage": f"{self.patient_info['fat_percentage']:.1f}",
"body_composition_chart": graphs.get("body_composition", ""), "body_composition_chart": graphs.get("body_composition", ""),
"body_fat_chart": graphs.get("body_fat_percent", ""), # Alias for template "body_fat_chart": graphs.get(
"body_fat_percent", ""
), # Alias for template
"body_fat_percent_chart": graphs.get( "body_fat_percent_chart": graphs.get(
"body_fat_percent", "" "body_fat_percent", ""
), # Keep for consistency ), # Keep for consistency
@@ -1199,29 +1223,29 @@ class ContextGenerator:
"weight_loss_rate": rmr_metrics.get("weight_loss_rate", 1.0), "weight_loss_rate": rmr_metrics.get("weight_loss_rate", 1.0),
"total_calories": rmr_metrics.get("total_calories", 1375), "total_calories": rmr_metrics.get("total_calories", 1375),
} }
# For minimal reports, also generate resting heart rate table for page_5 # For minimal reports, also generate resting heart rate table for page_5
if report_type == "minimal" and graph_generator: if report_type == "minimal" and graph_generator:
resting_hr_metrics = self._calculate_resting_heart_rate_metrics() resting_hr_metrics = self._calculate_resting_heart_rate_metrics()
rhr_table_info = self._calculate_rhr_table_data( rhr_table_info = self._calculate_rhr_table_data(
self.patient_info["age"], self.patient_info["gender"] self.patient_info["age"], self.patient_info["gender"]
) )
# Get resting heart rate value and determine category # Get resting heart rate value and determine category
rhr_value_str = resting_hr_metrics.get("resting_heart_rate", "0bpm") rhr_value_str = resting_hr_metrics.get("resting_heart_rate", "0bpm")
rhr_value = float(rhr_value_str.replace("bpm", "").strip()) rhr_value = float(rhr_value_str.replace("bpm", "").strip())
category = self._determine_rhr_category( category = self._determine_rhr_category(
rhr_value, rhr_value,
self.patient_info["age"], self.patient_info["age"],
self.patient_info["gender"], self.patient_info["gender"],
) )
gender_label = ( gender_label = (
"F" if self.patient_info["gender"].lower().startswith("f") else "M" "F" if self.patient_info["gender"].lower().startswith("f") else "M"
) )
age_range_label = f"{rhr_table_info['age_range']} ({gender_label})" age_range_label = f"{rhr_table_info['age_range']} ({gender_label})"
rhr_columns = [ rhr_columns = [
"Age", "Age",
"Poor", "Poor",
@@ -1244,7 +1268,7 @@ class ContextGenerator:
rhr_table_info["ranges"]["Athlete"], rhr_table_info["ranges"]["Athlete"],
] ]
] ]
contexts["page_5"]["rhr_table"] = ( contexts["page_5"]["rhr_table"] = (
graph_generator.generate_resting_heart_rate_table( graph_generator.generate_resting_heart_rate_table(
data=rhr_data, data=rhr_data,
@@ -1265,12 +1289,16 @@ class ContextGenerator:
"deficit_carbs": f"{int(rmr_metrics.get('total_calories', 1600) * 0.39 / 4)}g Carbs", "deficit_carbs": f"{int(rmr_metrics.get('total_calories', 1600) * 0.39 / 4)}g Carbs",
"deficit_fat": f"{int(rmr_metrics.get('total_calories', 1600) * 0.39 / 9)}g Fat", "deficit_fat": f"{int(rmr_metrics.get('total_calories', 1600) * 0.39 / 9)}g Fat",
"deficit_fiber": "24g Fibre", "deficit_fiber": "24g Fibre",
"refeed_weekday_calories": int(rmr_metrics.get("total_calories", 1600) * 0.85), "refeed_weekday_calories": int(
rmr_metrics.get("total_calories", 1600) * 0.85
),
"refeed_weekday_protein": f"{int(rmr_metrics.get('total_calories', 1600) * 0.85 * 0.22 / 4)}g Protein", "refeed_weekday_protein": f"{int(rmr_metrics.get('total_calories', 1600) * 0.85 * 0.22 / 4)}g Protein",
"refeed_weekday_carbs": f"{int(rmr_metrics.get('total_calories', 1600) * 0.85 * 0.39 / 4)}g Carbs", "refeed_weekday_carbs": f"{int(rmr_metrics.get('total_calories', 1600) * 0.85 * 0.39 / 4)}g Carbs",
"refeed_weekday_fat": f"{int(rmr_metrics.get('total_calories', 1600) * 0.85 * 0.39 / 9)}g Fat", "refeed_weekday_fat": f"{int(rmr_metrics.get('total_calories', 1600) * 0.85 * 0.39 / 9)}g Fat",
"refeed_weekday_fiber": "20g Fibre", "refeed_weekday_fiber": "20g Fibre",
"refeed_weekend_calories": int(rmr_metrics.get("total_calories", 1600) * 1.375), "refeed_weekend_calories": int(
rmr_metrics.get("total_calories", 1600) * 1.375
),
"refeed_weekend_protein": f"{int(rmr_metrics.get('total_calories', 1600) * 1.375 * 0.22 / 4)}g Protein", "refeed_weekend_protein": f"{int(rmr_metrics.get('total_calories', 1600) * 1.375 * 0.22 / 4)}g Protein",
"refeed_weekend_carbs": f"{int(rmr_metrics.get('total_calories', 1600) * 1.375 * 0.39 / 4)}g Carbs", "refeed_weekend_carbs": f"{int(rmr_metrics.get('total_calories', 1600) * 1.375 * 0.39 / 4)}g Carbs",
"refeed_weekend_fat": f"{int(rmr_metrics.get('total_calories', 1600) * 1.375 * 0.39 / 9)}g Fat", "refeed_weekend_fat": f"{int(rmr_metrics.get('total_calories', 1600) * 1.375 * 0.39 / 9)}g Fat",
@@ -1291,12 +1319,12 @@ class ContextGenerator:
# Page 7 # Page 7
contexts["page_7"] = { contexts["page_7"] = {
"peak_vt": f"{pnoe_metrics['peak_vt']:.2f}", "peak_vt": f"{pnoe_metrics['peak_vt']:.2f}",
"peak_vt_bpm": f"{int(pnoe_metrics['peak_vt_hr'])}", "peak_vt_bpm": f"{int(pnoe_metrics['peak_vt_hr'])}",
"fev1_percentage": f"{fev1_percentage:.1f}", "fev1_percentage": f"{fev1_percentage:.1f}",
"lung_analysis_chart": graphs.get("spirometry_chart", ""), "lung_analysis_chart": graphs.get("spirometry_chart", ""),
"respiratory_analysis_chart": graphs.get("respiratory", ""), "respiratory_analysis_chart": graphs.get("respiratory", ""),
} }
# Page 8 # Page 8
contexts["page_8"] = { contexts["page_8"] = {
@@ -1562,7 +1590,11 @@ class ContextGenerator:
} }
# For minimal reports, create combined context for page_19_20_minimal # For minimal reports, create combined context for page_19_20_minimal
if report_type == "minimal" and 19 in pages_to_generate and 20 in pages_to_generate: if (
report_type == "minimal"
and 19 in pages_to_generate
and 20 in pages_to_generate
):
contexts["page_19_20_minimal"] = { contexts["page_19_20_minimal"] = {
"patient_name": self.patient_info["name"], "patient_name": self.patient_info["name"],
"body_fat_percentage_chart": graphs.get( "body_fat_percentage_chart": graphs.get(
+23 -20
View File
@@ -2,7 +2,7 @@
"cells": [ "cells": [
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 1,
"id": "b18c1027", "id": "b18c1027",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
@@ -88,7 +88,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 3,
"id": "56a9d655", "id": "56a9d655",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
@@ -104,7 +104,10 @@
], ],
"source": [ "source": [
"import pandas as pd\n", "import pandas as pd\n",
"spirometry_df = pd.read_csv(\"data/spirometry_data.csv\")\n", "import os\n",
"\n",
"base_dir = os.path.dirname(os.path.abspath('.'))\n",
"spirometry_df = pd.read_csv(f\"{base_dir}/data/spirometry_data.csv\")\n",
"\n", "\n",
"fvc_best = spirometry_df.loc[spirometry_df['Parameters'] == 'FVC', 'Best'].values[0]\n", "fvc_best = spirometry_df.loc[spirometry_df['Parameters'] == 'FVC', 'Best'].values[0]\n",
"fvc_pred = spirometry_df.loc[spirometry_df['Parameters'] == 'FVC', '%Pred.'].values[0]\n", "fvc_pred = spirometry_df.loc[spirometry_df['Parameters'] == 'FVC', '%Pred.'].values[0]\n",
@@ -122,7 +125,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 4,
"id": "990f4b4f", "id": "990f4b4f",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
@@ -136,7 +139,7 @@
} }
], ],
"source": [ "source": [
"df = pd.read_csv('data/Pnoe_20250729_1550-Moran_Keirstyn.csv', delimiter=';')\n", "df = pd.read_csv(f'{base_dir}/data/Pnoe_20250729_1550-Moran_Keirstyn.csv', delimiter=';')\n",
"peak_vt = df['VT(l)'].max()\n", "peak_vt = df['VT(l)'].max()\n",
"max_vt_row = df.loc[df['VT(l)'].idxmax()]\n", "max_vt_row = df.loc[df['VT(l)'].idxmax()]\n",
"print(f\"Peak VT: {peak_vt}\")\n", "print(f\"Peak VT: {peak_vt}\")\n",
@@ -146,7 +149,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 19,
"id": "041cbc3d", "id": "041cbc3d",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
@@ -154,21 +157,21 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"Peak VT: 2.3770000000000002\n", "Peak VT: 2.3844444444444446\n",
"HR at Peak VT: 171.525\n" "HR at Peak VT: 172.80555555555554\n"
] ]
}, },
{ {
"name": "stderr", "name": "stderr",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"/tmp/ipykernel_69398/4157056299.py:3: FutureWarning: errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead\n", "/tmp/ipykernel_53922/361246798.py:3: FutureWarning: errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead\n",
" df = df.apply(pd.to_numeric, errors='ignore')\n" " df = df.apply(pd.to_numeric, errors='ignore')\n"
] ]
} }
], ],
"source": [ "source": [
"df = pd.read_csv('data/Pnoe_20250729_1550-Moran_Keirstyn.csv', delimiter=';')\n", "df = pd.read_csv(f'{base_dir}/data/Pnoe_20250729_1550-Moran_Keirstyn.csv', delimiter=';')\n",
"# Convert all columns to numeric where possible, coercing errors to NaN\n", "# Convert all columns to numeric where possible, coercing errors to NaN\n",
"df = df.apply(pd.to_numeric, errors='ignore')\n", "df = df.apply(pd.to_numeric, errors='ignore')\n",
"df['VO2 Pulse'] = df['VO2(ml/min)'] / df['HR(bpm)'] # VO2 Pulse in mL/beat\n", "df['VO2 Pulse'] = df['VO2(ml/min)'] / df['HR(bpm)'] # VO2 Pulse in mL/beat\n",
@@ -176,7 +179,7 @@
"df['CHO'] = df['EE(kcal/min)'] * df['CARBS(%)']/100\n", "df['CHO'] = df['EE(kcal/min)'] * df['CARBS(%)']/100\n",
"df['FAT'] = df['EE(kcal/min)'] * df['FAT(%)']/100\n", "df['FAT'] = df['EE(kcal/min)'] * df['FAT(%)']/100\n",
"# Smooth key columns using rolling window\n", "# Smooth key columns using rolling window\n",
"window_size = 10\n", "window_size = 9\n",
"\n", "\n",
"# List of columns to smooth\n", "# List of columns to smooth\n",
"columns_to_smooth = ['VO2(ml/min)', 'VCO2(ml/min)', 'HR(bpm)', 'VT(l)', 'BF(bpm)', 'VE(l/min)', 'VO2 Pulse', 'VO2 Breath', 'CHO', 'FAT']\n", "columns_to_smooth = ['VO2(ml/min)', 'VCO2(ml/min)', 'HR(bpm)', 'VT(l)', 'BF(bpm)', 'VE(l/min)', 'VO2 Pulse', 'VO2 Breath', 'CHO', 'FAT']\n",
@@ -195,7 +198,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 20,
"id": "de7cadd1", "id": "de7cadd1",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
@@ -203,7 +206,7 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"Percent FEV: 72.91411042944786\n" "Percent FEV: 73.14246762099523\n"
] ]
} }
], ],
@@ -214,7 +217,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 21,
"id": "cb972ed3", "id": "cb972ed3",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
@@ -311,13 +314,13 @@
"[1 rows x 147 columns]" "[1 rows x 147 columns]"
] ]
}, },
"execution_count": 11, "execution_count": 21,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
], ],
"source": [ "source": [
"personal_df = pd.read_excel('data/SECA body comp for all patients.xlsx')\n", "personal_df = pd.read_excel(f'{base_dir}/data/SECA body comp for all patients.xlsx')\n",
"\n", "\n",
"keirstyn_data = personal_df[personal_df['LastName'].str.contains('Moran', case=False, na=False)]\n", "keirstyn_data = personal_df[personal_df['LastName'].str.contains('Moran', case=False, na=False)]\n",
"keirstyn_data" "keirstyn_data"
@@ -325,7 +328,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 22,
"id": "98d9295a", "id": "98d9295a",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
@@ -333,7 +336,7 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"VO2 Max: 47.906290322580645\n" "VO2 Max: 48.19062126642772\n"
] ]
} }
], ],
@@ -823,7 +826,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "report_generation", "display_name": ".venv",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
@@ -837,7 +840,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.12.3" "version": "3.12.6"
} }
}, },
"nbformat": 4, "nbformat": 4,