Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 47e6e69eb7 | |||
| 8e8280bcb0 | |||
| 580ad5d248 | |||
| 974699dd81 | |||
| dbee12341a | |||
| 4028b7c626 | |||
| cc9e526fda | |||
| 79daa8cea1 | |||
| 9c1cb1966b | |||
| 29ad9e2265 | |||
| 47f0c6f3fb | |||
| 9d51b006c0 | |||
| 895d8abe02 | |||
| 2e4cc5ec76 | |||
| b186fafbba | |||
| af81e8d683 | |||
| fec2c72b13 | |||
| 32126a3702 |
@@ -0,0 +1 @@
|
||||
3.12
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 99 KiB |
@@ -233,6 +233,7 @@ async def upload_files(
|
||||
str(pnoe_path),
|
||||
str(spirometry_csv_path),
|
||||
None, # No SECA file needed anymore
|
||||
str(oxygenation_path) if oxygenation_path else None, # Oxygenation CSV
|
||||
)
|
||||
# Set patient info manually since we're not reading from SECA
|
||||
weight_kg = float(weight.replace("lbs", "").replace("kg", "").strip())
|
||||
@@ -442,6 +443,7 @@ async def edit_metrics(request: Request):
|
||||
str(pnoe_path),
|
||||
spirometry_csv_path,
|
||||
None, # No SECA file
|
||||
str(oxygenation_path) if oxygenation_path else None, # Oxygenation CSV
|
||||
)
|
||||
# Set patient info manually
|
||||
weight_str = patient_info.get("weight", "0")
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<!-- Name and Date Section -->
|
||||
<div class="text-right mt-16">
|
||||
<h2 class="text-4xl font-bold tracking-wider mb-2">
|
||||
{{ first_name|upper }}
|
||||
{{ name|upper }}
|
||||
</h2>
|
||||
<h2 class="text-4xl font-bold tracking-wider mb-6">
|
||||
{{ surname|upper }}
|
||||
|
||||
+511
-34
@@ -1,38 +1,515 @@
|
||||
<div class="w-full page bg-white">
|
||||
<!-- Main Content -->
|
||||
<div class="px-8 py-6">
|
||||
<!-- Page Title -->
|
||||
<h1 class="text-3xl font-bold text-black mb-6">Fuelling Analysis</h1>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="px-8 py-6">
|
||||
<!-- VO2 Pulse Section -->
|
||||
<div class="mb-8">
|
||||
<!-- VO2 Pulse Header -->
|
||||
<div class="bg-gray-200 p-4 rounded-lg mb-4 text-center">
|
||||
<h2 class="text-lg font-bold text-black">VO2 Pulse</h2>
|
||||
<p class="text-black">Begins to drop at {{ vo2_pulse_drop_bpm | default('180 bpm') }} ({{ vo2_pulse_drop_zone | default('Zone 4') }})</p>
|
||||
</div>
|
||||
|
||||
<!-- VO2 Pulse Graph -->
|
||||
<div class="flex justify-center mb-6">
|
||||
<img src="data:image/png;base64, {{ vo2_pulse_chart }}"
|
||||
alt="VO2 Pulse Chart"
|
||||
class="w-full max-w-4xl h-auto object-contain">
|
||||
</div>
|
||||
<!-- Flowchart Image -->
|
||||
<div class="mb-8 flex justify-center">
|
||||
<img
|
||||
src="data:image/png;base64,{{ fuelling_analysis_flowchart }}"
|
||||
alt="Fuelling Analysis Flowchart"
|
||||
class="w-full max-w-4xl h-auto object-contain"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Carbohydrate Storage Table -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-bold text-black mb-4 text-center">
|
||||
Estimated Carbohydrate Storage by Weight and Sex in Athletes
|
||||
</h2>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<table
|
||||
class="table-auto border-collapse border border-gray-400 text-sm"
|
||||
>
|
||||
<thead>
|
||||
<tr class="bg-gray-200">
|
||||
<th
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
Weight (kg)
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
Sex
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
Muscle Glycogen (g)
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
Liver Glycogen (g)
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
Blood Glucose (g)
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
Total Carb (g)
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
Total Carb (kcal)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
50
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
male
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
292
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
105
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
4.5
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
402
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
1608
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="bg-gray-50">
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
50
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
female
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
228
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
85
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
4.5
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
317
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
1268
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
60
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
male
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
351
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
105
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
4.5
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
460
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
1842
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="bg-gray-50">
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
60
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
female
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
273
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
85
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
4.5
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
362
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
1450
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
70
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
male
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
410
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
105
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
4.5
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
519
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
2076
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="bg-gray-50">
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
70
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
female
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
318
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
85
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
4.5
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
408
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
1632
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
80
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
male
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
468
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
105
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
4.5
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
578
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
2310
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="bg-gray-50">
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
80
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
female
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
364
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
85
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
4.5
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
454
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
1814
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
90
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
male
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
526
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
105
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
4.5
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
636
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
2544
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="bg-gray-50">
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
90
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
female
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
409
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
85
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
4.5
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
499
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
1996
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
100
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
male
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
585
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
105
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
4.5
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
694
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
2778
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="bg-gray-50">
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
100
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
female
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
455
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
85
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
4.5
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
544
|
||||
</td>
|
||||
<td
|
||||
class="border border-gray-400 px-4 py-2 text-center"
|
||||
>
|
||||
2178
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VO2 Breath Section -->
|
||||
<div class="mb-8">
|
||||
<!-- VO2 Breath Header -->
|
||||
<div class="bg-gray-200 p-4 rounded-lg mb-4 text-center">
|
||||
<h2 class="text-lg font-bold text-black">VO2 Breath</h2>
|
||||
<p class="text-black">Begins to drop at {{ vo2_breath_drop_bpm | default('173 bpm') }} ({{ vo2_breath_drop_zone | default('Zone 3') }})</p>
|
||||
</div>
|
||||
|
||||
<!-- VO2 Breath Graph -->
|
||||
<div class="flex justify-center mb-6">
|
||||
<img src="data:image/png;base64, {{ vo2_breath_chart }}"
|
||||
alt="VO2 Breath Chart"
|
||||
class="w-full max-w-4xl h-auto object-contain">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
+33
-226
@@ -1,231 +1,38 @@
|
||||
<div class="w-full page bg-white">
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="px-8 py-6">
|
||||
<!-- Fat Metabolism Section -->
|
||||
<div class="mb-2">
|
||||
<h2 class="text-xl font-bold text-black mb-4 text-center">
|
||||
Fat Metabolism
|
||||
</h2>
|
||||
|
||||
<!-- Fat Metabolism Info Boxes -->
|
||||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||
<!-- Fat Max Box -->
|
||||
<div class="bg-gray-200 p-3 rounded-lg text-center">
|
||||
<h3 class="text-base font-bold text-black mb-2">Fat Max</h3>
|
||||
<p class="text-xs text-gray-600 italic mb-2">
|
||||
{{ fat_max_optimal | default('*Optimal
|
||||
10-12Kcals/minute') }}
|
||||
</p>
|
||||
<p class="text-lg font-bold text-black">
|
||||
{{ fat_max_value | default('3.8Kcals/min') }}
|
||||
</p>
|
||||
<p class="text-xs text-black">
|
||||
{{ fat_max_heart_rate | default('49% of Max Heart Rate')
|
||||
}}
|
||||
</p>
|
||||
<p class="text-xs text-black">
|
||||
{{ fat_max_bpm | default('97 bpm') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Carbs and Fat Crossover Box -->
|
||||
<div class="bg-gray-200 p-3 rounded-lg text-center">
|
||||
<h3 class="text-base font-bold text-black mb-3">
|
||||
Carbs and Fat Crossover
|
||||
</h3>
|
||||
<p class="text-lg font-bold text-black">
|
||||
{{ crossover_bpm | default('100bpm') }}
|
||||
</p>
|
||||
<p class="text-xs text-black">
|
||||
{{ crossover_heart_rate | default('51% of Max Heart
|
||||
Rate') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fat Metabolism Graph -->
|
||||
<div class="mb-4">
|
||||
<div class="bg-gray-100 p-2 rounded-lg mb-2">
|
||||
<p class="text-black font-semibold text-center text-sm"></p>
|
||||
{{ fat_metabolism_note | default('100bpm at a speed of
|
||||
4.0mph and incline of 2%') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<img
|
||||
src="data:image/png;base64, {{ fat_metabolism_chart }}"
|
||||
alt="Fat Metabolism Chart"
|
||||
class="w-full max-w-4xl h-auto object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recovery Section -->
|
||||
<div class="mb-2">
|
||||
<h2 class="text-xl font-bold text-black mb-4 text-center">
|
||||
Recovery
|
||||
</h2>
|
||||
|
||||
<!-- Recovery Info Boxes -->
|
||||
<div class="grid grid-cols-3 gap-4 mb-4">
|
||||
<!-- Cardiac Recovery -->
|
||||
<div class="bg-gray-200 p-3 rounded-lg text-center">
|
||||
<h3 class="text-sm font-bold text-black mb-2">
|
||||
Cardiac Recovery
|
||||
</h3>
|
||||
<p class="text-xs text-gray-600 mb-2">
|
||||
{{ cardiac_recovery_time | default('(1 minute)') }}
|
||||
</p>
|
||||
<p class="text-lg font-bold text-black">
|
||||
{{ cardiac_recovery_percentage | default('33%') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Metabolic Recovery -->
|
||||
<div class="bg-gray-200 p-3 rounded-lg text-center">
|
||||
<h3 class="text-sm font-bold text-black mb-2">
|
||||
Metabolic (CO2) Recovery
|
||||
</h3>
|
||||
<p class="text-xs text-gray-600 mb-2">
|
||||
{{ metabolic_recovery_time | default('(2 minute)') }}
|
||||
</p>
|
||||
<p class="text-lg font-bold text-black">
|
||||
{{ metabolic_recovery_percentage | default('65%') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Breath Frequency Recovery -->
|
||||
<div class="bg-gray-200 p-3 rounded-lg text-center">
|
||||
<h3 class="text-sm font-bold text-black mb-2">
|
||||
Breath Frequency Recovery
|
||||
</h3>
|
||||
<p class="text-xs text-gray-600 mb-2">
|
||||
{{ breath_recovery_time | default('(2.5 minute)') }}
|
||||
</p>
|
||||
<p class="text-lg font-bold text-black">
|
||||
{{ breath_recovery_percentage | default('76%') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recovery Graph -->
|
||||
<div class="flex justify-center mb-6">
|
||||
<img
|
||||
src="data:image/png;base64, {{ recovery_chart }}"
|
||||
alt="Recovery Chart"
|
||||
class="w-full max-w-4xl h-auto object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resting Heart Rate Table -->
|
||||
<div class="mb-2">
|
||||
<h3 class="text-base font-bold text-black mb-2 text-center">
|
||||
Resting Heart Rate - {{ resting_heart_rate | default('53bpm') }}
|
||||
</h3>
|
||||
|
||||
<table class="w-full border-collapse text-xs">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold"
|
||||
>
|
||||
Age (F)
|
||||
</th>
|
||||
<th
|
||||
class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold"
|
||||
>
|
||||
Poor
|
||||
</th>
|
||||
<th
|
||||
class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold"
|
||||
>
|
||||
Below Average
|
||||
</th>
|
||||
<th
|
||||
class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold"
|
||||
>
|
||||
Average
|
||||
</th>
|
||||
<th
|
||||
class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold"
|
||||
>
|
||||
Above Average
|
||||
</th>
|
||||
<th
|
||||
class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold"
|
||||
>
|
||||
Good
|
||||
</th>
|
||||
<th
|
||||
class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold"
|
||||
>
|
||||
Excellent
|
||||
</th>
|
||||
<th
|
||||
class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold relative"
|
||||
>
|
||||
Athlete
|
||||
<!-- Arrow indicator -->
|
||||
<div
|
||||
class="absolute -bottom-3 left-1/2 transform -translate-x-1/2"
|
||||
>
|
||||
<div
|
||||
class="w-0 h-0 border-l-3 border-r-3 border-t-6 border-transparent border-t-black"
|
||||
></div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="bg-cyan-200 border border-gray-400 p-2 text-black font-semibold text-center"
|
||||
>
|
||||
{{ hr_age_range | default('26-35') }}
|
||||
</td>
|
||||
<td
|
||||
class="bg-gray-100 border border-gray-400 p-2 text-black text-center"
|
||||
>
|
||||
{{ hr_poor | default('82bpm +') }}
|
||||
</td>
|
||||
<td
|
||||
class="bg-gray-100 border border-gray-400 p-2 text-black text-center"
|
||||
>
|
||||
{{ hr_below_avg | default('75-81bpm') }}
|
||||
</td>
|
||||
<td
|
||||
class="bg-gray-100 border border-gray-400 p-2 text-black text-center"
|
||||
>
|
||||
{{ hr_average | default('71-74bpm') }}
|
||||
</td>
|
||||
<td
|
||||
class="bg-gray-100 border border-gray-400 p-2 text-black text-center"
|
||||
>
|
||||
{{ hr_above_avg | default('66-70bpm') }}
|
||||
</td>
|
||||
<td
|
||||
class="bg-gray-100 border border-gray-400 p-2 text-black text-center"
|
||||
>
|
||||
{{ hr_good | default('62-65bpm') }}
|
||||
</td>
|
||||
<td
|
||||
class="bg-gray-100 border border-gray-400 p-2 text-black text-center"
|
||||
>
|
||||
{{ hr_excellent | default('55-61bpm') }}
|
||||
</td>
|
||||
<td
|
||||
class="bg-green-200 border border-gray-400 p-2 text-black text-center font-bold"
|
||||
>
|
||||
{{ hr_athlete | default('44-54bpm') }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Main Content -->
|
||||
<div class="px-8 py-6">
|
||||
<!-- VO2 Pulse Section -->
|
||||
<div class="mb-8">
|
||||
<!-- VO2 Pulse Header -->
|
||||
<div class="bg-gray-200 p-4 rounded-lg mb-4 text-center">
|
||||
<h2 class="text-lg font-bold text-black">VO2 Pulse</h2>
|
||||
<p class="text-black">Begins to drop at {{ vo2_pulse_drop_bpm | default('180 bpm') }} ({{ vo2_pulse_drop_zone | default('Zone 4') }})</p>
|
||||
</div>
|
||||
|
||||
<!-- VO2 Pulse Graph -->
|
||||
<div class="flex justify-center mb-6">
|
||||
<img src="data:image/png;base64, {{ vo2_pulse_chart }}"
|
||||
alt="VO2 Pulse Chart"
|
||||
class="w-full max-w-4xl h-auto object-contain">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VO2 Breath Section -->
|
||||
<div class="mb-8">
|
||||
<!-- VO2 Breath Header -->
|
||||
<div class="bg-gray-200 p-4 rounded-lg mb-4 text-center">
|
||||
<h2 class="text-lg font-bold text-black">VO2 Breath</h2>
|
||||
<p class="text-black">Begins to drop at {{ vo2_breath_drop_bpm | default('173 bpm') }} ({{ vo2_breath_drop_zone | default('Zone 3') }})</p>
|
||||
</div>
|
||||
|
||||
<!-- VO2 Breath Graph -->
|
||||
<div class="flex justify-center mb-6">
|
||||
<img src="data:image/png;base64, {{ vo2_breath_chart }}"
|
||||
alt="VO2 Breath Chart"
|
||||
class="w-full max-w-4xl h-auto object-contain">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
+127
-67
@@ -1,76 +1,136 @@
|
||||
<div class="page bg-white p-8 max-w-4xl mx-auto">
|
||||
<!-- Header -->
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-4">Local Muscle Activity</h1>
|
||||
<h2 class="text-xl font-semibold text-gray-800 mb-2">Muscle Oxygenation Assessment</h2>
|
||||
<p class="text-sm text-gray-600 leading-relaxed">
|
||||
SMO2 testing (Skeletal Muscle Oxygen Saturation) is an analysis of how effectively oxygen is being used at a particular muscle. It helps determine limitations on if the muscle is effectively using oxygen when exercising.
|
||||
</p>
|
||||
</div>
|
||||
<div class="w-full page bg-white">
|
||||
|
||||
<!-- Right Leg Section -->
|
||||
<div class="mb-12">
|
||||
<h3 class="text-lg font-semibold text-center text-gray-800 mb-6">Indications - Right Leg</h3>
|
||||
|
||||
<div class="flex gap-8">
|
||||
<!-- Chart Image -->
|
||||
<div class="flex-1">
|
||||
<img src="right-leg-chart.png" alt="Right Leg SMO2 Chart" class="w-full h-auto">
|
||||
<!-- Main Content -->
|
||||
<div class="px-8 py-6">
|
||||
<!-- Fat Metabolism Section -->
|
||||
<div class="mb-2">
|
||||
<h2 class="text-xl font-bold text-black mb-4 text-center">
|
||||
Fat Metabolism
|
||||
</h2>
|
||||
|
||||
<!-- Fat Metabolism Info Boxes -->
|
||||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||
<!-- Fat Max Box -->
|
||||
<div class="bg-gray-200 p-3 rounded-lg text-center">
|
||||
<h3 class="text-base font-bold text-black mb-2">Fat Max</h3>
|
||||
<p class="text-xs text-gray-600 italic mb-2">
|
||||
{{ fat_max_optimal | default('*Optimal
|
||||
10-12Kcals/minute') }}
|
||||
</p>
|
||||
<p class="text-lg font-bold text-black">
|
||||
{{ fat_max_value | default('3.8Kcals/min') }}
|
||||
</p>
|
||||
<p class="text-xs text-black">
|
||||
{{ fat_max_heart_rate | default('49% of Max Heart Rate')
|
||||
}}
|
||||
</p>
|
||||
<p class="text-xs text-black">
|
||||
{{ fat_max_bpm | default('97 bpm') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Carbs and Fat Crossover Box -->
|
||||
<div class="bg-gray-200 p-3 rounded-lg text-center">
|
||||
<h3 class="text-base font-bold text-black mb-3">
|
||||
Carbs and Fat Crossover
|
||||
</h3>
|
||||
<p class="text-lg font-bold text-black">
|
||||
{{ crossover_bpm | default('100bpm') }}
|
||||
</p>
|
||||
<p class="text-xs text-black">
|
||||
{{ crossover_heart_rate | default('51% of Max Heart
|
||||
Rate') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Side Info -->
|
||||
<div class="w-48 space-y-4">
|
||||
<div class="bg-gray-100 p-3 rounded">
|
||||
<div class="text-xs font-semibold text-gray-700 mb-1">Surplus</div>
|
||||
<div class="text-xs text-gray-600">Supply > Demand at a heart rate and speed of:</div>
|
||||
<div class="text-sm font-bold text-gray-800">n/a</div>
|
||||
|
||||
<!-- Fat Metabolism Graph -->
|
||||
<div class="mb-4">
|
||||
<!-- <div class="bg-gray-100 p-2 rounded-lg mb-2">
|
||||
<p class="text-black font-semibold text-center text-sm"></p>
|
||||
{{ fat_metabolism_note | default('100bpm at a speed of
|
||||
4.0mph and incline of 2%') }}
|
||||
</p>
|
||||
</div> -->
|
||||
|
||||
<div class="flex justify-center">
|
||||
<img
|
||||
src="data:image/png;base64, {{ fat_metabolism_chart }}"
|
||||
alt="Fat Metabolism Chart"
|
||||
class="w-full max-w-4xl h-auto object-contain"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-100 p-3 rounded">
|
||||
<div class="text-xs font-semibold text-gray-700 mb-1">Supply Threshold</div>
|
||||
<div class="text-xs text-gray-600">Demand outstrips supply at a heart rate of:</div>
|
||||
<div class="text-sm font-bold text-gray-800">154bpm @ 5.0mph</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recovery Section -->
|
||||
<div class="mb-2">
|
||||
<h2 class="text-xl font-bold text-black mb-4 text-center">
|
||||
Recovery
|
||||
</h2>
|
||||
|
||||
<!-- Recovery Info Boxes -->
|
||||
<div class="grid grid-cols-3 gap-4 mb-4">
|
||||
<!-- Cardiac Recovery -->
|
||||
<div class="bg-gray-200 p-3 rounded-lg text-center">
|
||||
<h3 class="text-sm font-bold text-black mb-2">
|
||||
Cardiac Recovery
|
||||
</h3>
|
||||
<p class="text-xs text-gray-600 mb-2">
|
||||
{{ cardiac_recovery_time | default('(1 minute)') }}
|
||||
</p>
|
||||
<p class="text-lg font-bold text-black">
|
||||
{{ cardiac_recovery_percentage | default('33%') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-100 p-3 rounded">
|
||||
<div class="text-xs font-semibold text-gray-700 mb-1">Recovery</div>
|
||||
<div class="text-xs text-gray-600">"Optimal >100%"</div>
|
||||
<div class="text-sm font-bold text-gray-800">n/a</div>
|
||||
|
||||
<!-- Metabolic Recovery -->
|
||||
<div class="bg-gray-200 p-3 rounded-lg text-center">
|
||||
<h3 class="text-sm font-bold text-black mb-2">
|
||||
Metabolic (CO2) Recovery
|
||||
</h3>
|
||||
<p class="text-xs text-gray-600 mb-2">
|
||||
{{ metabolic_recovery_time | default('(2 minute)') }}
|
||||
</p>
|
||||
<p class="text-lg font-bold text-black">
|
||||
{{ metabolic_recovery_percentage | default('65%') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Breath Frequency Recovery -->
|
||||
<div class="bg-gray-200 p-3 rounded-lg text-center">
|
||||
<h3 class="text-sm font-bold text-black mb-2">
|
||||
Breath Frequency Recovery
|
||||
</h3>
|
||||
<p class="text-xs text-gray-600 mb-2">
|
||||
{{ breath_recovery_time | default('(2.5 minute)') }}
|
||||
</p>
|
||||
<p class="text-lg font-bold text-black">
|
||||
{{ breath_recovery_percentage | default('76%') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recovery Graph -->
|
||||
<div class="flex justify-center mb-6">
|
||||
<img
|
||||
src="data:image/png;base64, {{ recovery_chart }}"
|
||||
alt="Recovery Chart"
|
||||
class="w-full max-w-4xl h-auto object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resting Heart Rate Table -->
|
||||
<div class="mb-2">
|
||||
<div class="flex justify-center">
|
||||
<img
|
||||
src="data:image/png;base64, {{ rhr_table }}"
|
||||
alt="Resting Heart Rate Table"
|
||||
class="table-image"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Left Leg Section -->
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-center text-gray-800 mb-6">Indications - Left Leg</h3>
|
||||
|
||||
<div class="flex gap-8">
|
||||
<!-- Chart Image -->
|
||||
<div class="flex-1">
|
||||
<img src="left-leg-chart.png" alt="Left Leg SMO2 Chart" class="w-full h-auto">
|
||||
</div>
|
||||
|
||||
<!-- Right Side Info -->
|
||||
<div class="w-48 space-y-4">
|
||||
<div class="bg-gray-100 p-3 rounded">
|
||||
<div class="text-xs font-semibold text-gray-700 mb-1">Surplus</div>
|
||||
<div class="text-xs text-gray-600">Supply > Demand at a heart rate and speed of:</div>
|
||||
<div class="text-sm font-bold text-gray-800">n/a</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-100 p-3 rounded">
|
||||
<div class="text-xs font-semibold text-gray-700 mb-1">Supply Threshold</div>
|
||||
<div class="text-xs text-gray-600">Demand outstrips supply at a heart rate of:</div>
|
||||
<div class="text-sm font-bold text-gray-800">165 bpm @ 5.5mph</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-100 p-3 rounded">
|
||||
<div class="text-xs font-semibold text-gray-700 mb-1">Recovery</div>
|
||||
<div class="text-xs text-gray-600">"Optimal >100%"</div>
|
||||
<div class="text-sm font-bold text-gray-800">n/a</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+162
-212
@@ -1,216 +1,166 @@
|
||||
<div class="w-full page bg-white">
|
||||
<!-- Header Section -->
|
||||
<!-- Main Content -->
|
||||
<div class="px-8 py-6">
|
||||
<!-- Page Title -->
|
||||
<h1 class="text-3xl font-bold text-black mb-8 text-center">Training Recommendations</h1>
|
||||
|
||||
<!-- Training Recommendations Section -->
|
||||
<div class="grid grid-cols-2 gap-8 mb-8">
|
||||
<!-- Left Side: Zone Recommendations -->
|
||||
<div class="bg-gray-200 p-6 rounded-lg">
|
||||
<!-- Zone 2 Recommendations -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-bold text-black mb-3">Zone 2 {{ zone2_frequency | default('3-4x/week') }}:</h3>
|
||||
<ul class="text-sm text-black space-y-1 list-disc list-inside">
|
||||
<li>{{ zone2_duration | default('40+ minutes') }} of Steady State Cardio (HR {{ zone2_hr_range | default('____') }} bpm)</li>
|
||||
<li>{{ zone2_speed | default('____ mph') }} at {{ zone2_incline | default('2% Incline') }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Zone 3 Recommendations -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-bold text-black mb-3">Zone 3 {{ zone3_frequency | default('1-2x/week') }}:</h3>
|
||||
<ul class="text-sm text-black space-y-1 list-disc list-inside">
|
||||
<li>{{ zone3_duration | default('10-20 minutes') }} in zone 3 (HR {{ zone3_hr_range | default('____ bpm') }})</li>
|
||||
<li>{{ zone3_speed | default('____mph') }} + at {{ zone3_incline | default('2% Incline') }}</li>
|
||||
<li>Slow down cadence until HR reaches {{ zone3_target_hr | default('___ bpm') }}</li>
|
||||
<li>{{ zone3_recovery_speed | default('____mph') }} at {{ zone3_recovery_incline | default('2% Incline') }}</li>
|
||||
<li>Maintain HR in zone 1 ({{ zone1_hr_range | default('____bpm') }}) for {{ zone1_duration | default('4-8 minutes') }}</li>
|
||||
<li>Repeat {{ zone3_repeats | default('2-3 times') }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Side: Training Table -->
|
||||
<div>
|
||||
<table class="w-full border-collapse text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold">Type</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold">Sets</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold">Effort Duration</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold">Zone</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold">RPE</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold">Recovery Duration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Short Row -->
|
||||
<tr>
|
||||
<td class="border border-gray-400 p-2 text-center text-black font-semibold">Short</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ short_sets | default('8-10') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ short_duration | default('10-30 seconds') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ short_zone | default('5') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ short_rpe | default('10') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ short_recovery | default('20-60 seconds') }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Medium Row -->
|
||||
<tr>
|
||||
<td class="border border-gray-400 p-2 text-center text-black font-semibold">Medium</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ medium_sets | default('6-8') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ medium_duration | default('30-90 seconds') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ medium_zone | default('4') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ medium_rpe | default('8-9') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ medium_recovery | default('30-90 seconds') }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Long Row -->
|
||||
<tr>
|
||||
<td class="border border-gray-400 p-2 text-center text-black font-semibold">Long</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ long_sets | default('4-6') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ long_duration | default('5-10 minutes') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ long_zone | default('3/4') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ long_rpe | default('7-8') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ long_recovery | default('2.5-5 minutes') }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Tempo Row -->
|
||||
<tr>
|
||||
<td class="border border-gray-400 p-2 text-center text-black font-semibold">Tempo</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ tempo_sets | default('2-3') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ tempo_duration | default('10-20 minutes') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ tempo_zone | default('3') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ tempo_rpe | default('6-7') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ tempo_recovery | default('4-8 minutes') }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Cardio Row -->
|
||||
<tr>
|
||||
<td class="border border-gray-400 p-2 text-center text-black font-semibold">Cardio</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ cardio_sets | default('1') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ cardio_duration | default('>40 minutes') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ cardio_zone | default('2') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ cardio_rpe | default('4-5') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ cardio_recovery | default('N/A') }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Training Week Example Section -->
|
||||
<div class="page bg-white p-8 max-w-4xl mx-auto">
|
||||
<!-- Header -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-2xl font-bold text-black mb-6 text-center">Training Week Example with Progression</h2>
|
||||
|
||||
<!-- Week 1 -->
|
||||
<div class="mb-6">
|
||||
<div class="grid grid-cols-7 gap-2">
|
||||
<!-- Monday -->
|
||||
<div class="bg-cyan-300 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Monday</div>
|
||||
<div class="text-sm text-black">{{ week1_mon_zone | default('Zone 2') }}</div>
|
||||
<div class="text-sm text-black">{{ week1_mon_duration | default('45 mins') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Tuesday -->
|
||||
<div class="bg-cyan-300 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Tuesday</div>
|
||||
<div class="text-sm text-black">{{ week1_tue_zone | default('Zone 2') }}</div>
|
||||
<div class="text-sm text-black">{{ week1_tue_duration | default('45 mins') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Wednesday -->
|
||||
<div class="bg-cyan-300 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Wednesday</div>
|
||||
<div class="text-sm text-black">{{ week1_wed_zone | default('Zone 3') }}</div>
|
||||
<div class="text-sm text-black">{{ week1_wed_duration1 | default('10mins On') }}</div>
|
||||
<div class="text-sm text-black">{{ week1_wed_duration2 | default('8mins Rest') }}</div>
|
||||
<div class="text-sm text-black">{{ week1_wed_sets | default('x2') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Thursday -->
|
||||
<div class="bg-gray-200 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Thursday</div>
|
||||
<div class="text-sm text-black">{{ week1_thu_content | default('') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Friday -->
|
||||
<div class="bg-cyan-300 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Friday</div>
|
||||
<div class="text-sm text-black">{{ week1_fri_zone | default('Zone 2') }}</div>
|
||||
<div class="text-sm text-black">{{ week1_fri_duration | default('45 mins') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Saturday -->
|
||||
<div class="bg-gray-200 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Saturday</div>
|
||||
<div class="text-sm text-black">{{ week1_sat_content | default('') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Sunday -->
|
||||
<div class="bg-gray-200 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Sunday</div>
|
||||
<div class="text-sm text-black">{{ week1_sun_content | default('') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Week 2 -->
|
||||
<div class="mb-6">
|
||||
<div class="grid grid-cols-7 gap-2">
|
||||
<!-- Monday -->
|
||||
<div class="bg-cyan-300 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Monday</div>
|
||||
<div class="text-sm text-black">{{ week2_mon_zone | default('Zone 2') }}</div>
|
||||
<div class="text-sm text-black">{{ week2_mon_duration | default('50 mins') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Tuesday -->
|
||||
<div class="bg-cyan-300 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Tuesday</div>
|
||||
<div class="text-sm text-black">{{ week2_tue_zone | default('Zone 2') }}</div>
|
||||
<div class="text-sm text-black">{{ week2_tue_duration | default('50 mins') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Wednesday -->
|
||||
<div class="bg-cyan-300 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Wednesday</div>
|
||||
<div class="text-sm text-black">{{ week2_wed_zone | default('Zone 3') }}</div>
|
||||
<div class="text-sm text-black">{{ week2_wed_duration1 | default('10mins On') }}</div>
|
||||
<div class="text-sm text-black">{{ week2_wed_duration2 | default('6mins Rest') }}</div>
|
||||
<div class="text-sm text-black">{{ week2_wed_sets | default('x2') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Thursday -->
|
||||
<div class="bg-gray-200 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Thursday</div>
|
||||
<div class="text-sm text-black">{{ week2_thu_content | default('') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Friday -->
|
||||
<div class="bg-cyan-300 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Friday</div>
|
||||
<div class="text-sm text-black">{{ week2_fri_zone | default('Zone 2') }}</div>
|
||||
<div class="text-sm text-black">{{ week2_fri_duration | default('50 mins') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Saturday -->
|
||||
<div class="bg-gray-200 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Saturday</div>
|
||||
<div class="text-sm text-black">{{ week2_sat_content | default('') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Sunday -->
|
||||
<div class="bg-gray-200 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Sunday</div>
|
||||
<div class="text-sm text-black">{{ week2_sun_content | default('') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-4">
|
||||
Local Muscle Activity
|
||||
</h1>
|
||||
<h2 class="text-xl font-semibold text-gray-800 mb-2">
|
||||
Muscle Oxygenation Assessment
|
||||
</h2>
|
||||
<p class="text-sm text-gray-600 leading-relaxed">
|
||||
SMO2 testing (Skeletal Muscle Oxygen Saturation) is an analysis of
|
||||
how effectively oxygen is being used at a particular muscle. It
|
||||
helps determine limitations on if the muscle is effectively using
|
||||
oxygen when exercising.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Section -->
|
||||
<!-- Combined Muscle Oxygenation Chart -->
|
||||
<div class="mb-6">
|
||||
<div class="flex justify-center mb-4">
|
||||
<img
|
||||
src="data:image/png;base64,{{ muscle_oxygenation_chart }}"
|
||||
alt="Muscle Oxygenation Chart"
|
||||
class="w-full h-auto max-w-6xl"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Metrics Summary Grid -->
|
||||
<div class="grid grid-cols-2 gap-6 mb-6">
|
||||
<!-- Left Leg Metrics -->
|
||||
<div class="bg-blue-50 p-4 rounded-lg border-2 border-blue-200">
|
||||
<h3 class="text-lg font-bold text-gray-900 mb-4 text-center">
|
||||
Left Leg Analysis
|
||||
</h3>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="bg-white p-3 rounded shadow-sm">
|
||||
<div class="text-xs font-semibold text-gray-700 mb-1">
|
||||
Baseline SmO₂
|
||||
</div>
|
||||
<div class="text-lg font-bold text-gray-900">
|
||||
{{ left_baseline_smo2 | default('75.4%') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3 rounded shadow-sm">
|
||||
<div class="text-xs font-semibold text-gray-700 mb-1">
|
||||
Minimum SmO₂
|
||||
</div>
|
||||
<div class="text-lg font-bold text-gray-900">
|
||||
{{ left_minimum_smo2 | default('69.3%') }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 mt-1">
|
||||
{{ left_minimum_lap | default('Lap 6') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3 rounded shadow-sm">
|
||||
<div class="text-xs font-semibold text-gray-700 mb-1">
|
||||
Oxygen Drop
|
||||
</div>
|
||||
<div class="text-lg font-bold text-gray-900">
|
||||
{{ left_oxygen_drop | default('6.0%') }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 mt-1">
|
||||
{{ left_drop_percentage | default('8% decrease') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3 rounded shadow-sm">
|
||||
<div class="text-xs font-semibold text-gray-700 mb-1">
|
||||
Recovery
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 mb-1">
|
||||
"Optimal >100%"
|
||||
</div>
|
||||
<div class="text-lg font-bold text-green-600">
|
||||
{{ left_recovery_percentage | default('109%') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Leg Metrics -->
|
||||
<div class="bg-purple-50 p-4 rounded-lg border-2 border-purple-200">
|
||||
<h3 class="text-lg font-bold text-gray-900 mb-4 text-center">
|
||||
Right Leg Analysis
|
||||
</h3>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="bg-white p-3 rounded shadow-sm">
|
||||
<div class="text-xs font-semibold text-gray-700 mb-1">
|
||||
Baseline SmO₂
|
||||
</div>
|
||||
<div class="text-lg font-bold text-gray-900">
|
||||
{{ right_baseline_smo2 | default('82.9%') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3 rounded shadow-sm">
|
||||
<div class="text-xs font-semibold text-gray-700 mb-1">
|
||||
Minimum SmO₂
|
||||
</div>
|
||||
<div class="text-lg font-bold text-gray-900">
|
||||
{{ right_minimum_smo2 | default('73.7%') }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 mt-1">
|
||||
{{ right_minimum_lap | default('Lap 6') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3 rounded shadow-sm">
|
||||
<div class="text-xs font-semibold text-gray-700 mb-1">
|
||||
Oxygen Drop
|
||||
</div>
|
||||
<div class="text-lg font-bold text-gray-900">
|
||||
{{ right_oxygen_drop | default('9.3%') }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 mt-1">
|
||||
{{ right_drop_percentage | default('11% decrease') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-3 rounded shadow-sm">
|
||||
<div class="text-xs font-semibold text-gray-700 mb-1">
|
||||
Recovery
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 mb-1">
|
||||
"Optimal >100%"
|
||||
</div>
|
||||
<div class="text-lg font-bold text-blue-600">
|
||||
{{ right_recovery_percentage | default('97%') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Key Findings Summary -->
|
||||
<div class="bg-gray-100 p-4 rounded-lg">
|
||||
<h3 class="text-base font-bold text-gray-900 mb-3">Key Findings</h3>
|
||||
<div class="text-sm text-gray-700 space-y-2">
|
||||
<p>
|
||||
• <strong>Left leg</strong> showed better oxygen maintenance
|
||||
during high-intensity work
|
||||
</p>
|
||||
<p>
|
||||
•
|
||||
<strong
|
||||
>{{ recovery_assessment | default('Excellent recovery
|
||||
capacity') }}</strong
|
||||
>
|
||||
- both legs recovered well
|
||||
</p>
|
||||
<p>
|
||||
• <strong>Heart rate progression:</strong> {{ hr_warmup |
|
||||
default('93') }} → {{ hr_max | default('168') }} bpm
|
||||
</p>
|
||||
<p>
|
||||
• <strong>Test duration:</strong> {{ test_duration |
|
||||
default('~21 minutes active test') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+196
-128
@@ -1,148 +1,216 @@
|
||||
<div class="w-full page bg-white">
|
||||
|
||||
<!-- Header Section -->
|
||||
<!-- Main Content -->
|
||||
<div class="px-8 py-6">
|
||||
<!-- Page Title -->
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-3xl font-bold text-black mb-2">Training Week</h1>
|
||||
<p class="text-gray-600 italic">(To be filled out by your trainer)</p>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold text-black mb-8 text-center">Training Recommendations</h1>
|
||||
|
||||
<!-- First Training Week Grid -->
|
||||
<div class="mb-8">
|
||||
<div class="grid grid-cols-7 gap-2 mb-4">
|
||||
<!-- Week 1 Headers -->
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Monday</div>
|
||||
<!-- Training Recommendations Section -->
|
||||
<div class="grid grid-cols-2 gap-8 mb-8">
|
||||
<!-- Left Side: Zone Recommendations -->
|
||||
<div class="bg-gray-200 p-6 rounded-lg">
|
||||
<!-- Zone 2 Recommendations -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-bold text-black mb-3">Zone 2 {{ zone2_frequency | default('3-4x/week') }}:</h3>
|
||||
<ul class="text-sm text-black space-y-1 list-disc list-inside">
|
||||
<li>{{ zone2_duration | default('40+ minutes') }} of Steady State Cardio (HR {{ zone2_hr_range | default('____') }} bpm)</li>
|
||||
<li>{{ zone2_speed | default('____ mph') }} at {{ zone2_incline | default('2% Incline') }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Tuesday</div>
|
||||
</div>
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Wednesday</div>
|
||||
</div>
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Thursday</div>
|
||||
</div>
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Friday</div>
|
||||
</div>
|
||||
<div class="bg-gray-200 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Saturday</div>
|
||||
</div>
|
||||
<div class="bg-gray-200 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Sunday</div>
|
||||
|
||||
<!-- Zone 3 Recommendations -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-bold text-black mb-3">Zone 3 {{ zone3_frequency | default('1-2x/week') }}:</h3>
|
||||
<ul class="text-sm text-black space-y-1 list-disc list-inside">
|
||||
<li>{{ zone3_duration | default('10-20 minutes') }} in zone 3 (HR {{ zone3_hr_range | default('____ bpm') }})</li>
|
||||
<li>{{ zone3_speed | default('____mph') }} + at {{ zone3_incline | default('2% Incline') }}</li>
|
||||
<li>Slow down cadence until HR reaches {{ zone3_target_hr | default('___ bpm') }}</li>
|
||||
<li>{{ zone3_recovery_speed | default('____mph') }} at {{ zone3_recovery_incline | default('2% Incline') }}</li>
|
||||
<li>Maintain HR in zone 1 ({{ zone1_hr_range | default('____bpm') }}) for {{ zone1_duration | default('4-8 minutes') }}</li>
|
||||
<li>Repeat {{ zone3_repeats | default('2-3 times') }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Week 1 Content Boxes -->
|
||||
<div class="grid grid-cols-7 gap-2 mb-8">
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
|
||||
<!-- Right Side: Training Table -->
|
||||
<div>
|
||||
<table class="w-full border-collapse text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold">Type</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold">Sets</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold">Effort Duration</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold">Zone</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold">RPE</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold">Recovery Duration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Short Row -->
|
||||
<tr>
|
||||
<td class="border border-gray-400 p-2 text-center text-black font-semibold">Short</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ short_sets | default('8-10') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ short_duration | default('10-30 seconds') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ short_zone | default('5') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ short_rpe | default('10') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ short_recovery | default('20-60 seconds') }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Medium Row -->
|
||||
<tr>
|
||||
<td class="border border-gray-400 p-2 text-center text-black font-semibold">Medium</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ medium_sets | default('6-8') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ medium_duration | default('30-90 seconds') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ medium_zone | default('4') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ medium_rpe | default('8-9') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ medium_recovery | default('30-90 seconds') }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Long Row -->
|
||||
<tr>
|
||||
<td class="border border-gray-400 p-2 text-center text-black font-semibold">Long</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ long_sets | default('4-6') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ long_duration | default('5-10 minutes') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ long_zone | default('3/4') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ long_rpe | default('7-8') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ long_recovery | default('2.5-5 minutes') }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Tempo Row -->
|
||||
<tr>
|
||||
<td class="border border-gray-400 p-2 text-center text-black font-semibold">Tempo</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ tempo_sets | default('2-3') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ tempo_duration | default('10-20 minutes') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ tempo_zone | default('3') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ tempo_rpe | default('6-7') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ tempo_recovery | default('4-8 minutes') }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Cardio Row -->
|
||||
<tr>
|
||||
<td class="border border-gray-400 p-2 text-center text-black font-semibold">Cardio</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ cardio_sets | default('1') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ cardio_duration | default('>40 minutes') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ cardio_zone | default('2') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ cardio_rpe | default('4-5') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ cardio_recovery | default('N/A') }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Second Training Week Grid -->
|
||||
<!-- Training Week Example Section -->
|
||||
<div class="mb-8">
|
||||
<div class="grid grid-cols-7 gap-2 mb-4">
|
||||
<!-- Week 2 Headers -->
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Monday</div>
|
||||
</div>
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Tuesday</div>
|
||||
</div>
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Wednesday</div>
|
||||
</div>
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Thursday</div>
|
||||
</div>
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Friday</div>
|
||||
</div>
|
||||
<div class="bg-gray-200 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Saturday</div>
|
||||
</div>
|
||||
<div class="bg-gray-200 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Sunday</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="text-2xl font-bold text-black mb-6 text-center">Training Week Example with Progression</h2>
|
||||
|
||||
<!-- Week 2 Content Boxes -->
|
||||
<div class="grid grid-cols-7 gap-2 mb-8">
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<!-- Week 1 -->
|
||||
<div class="mb-6">
|
||||
<div class="grid grid-cols-7 gap-2">
|
||||
<!-- Monday -->
|
||||
<div class="bg-cyan-300 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Monday</div>
|
||||
<div class="text-sm text-black">{{ week1_mon_zone | default('Zone 2') }}</div>
|
||||
<div class="text-sm text-black">{{ week1_mon_duration | default('45 mins') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Tuesday -->
|
||||
<div class="bg-cyan-300 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Tuesday</div>
|
||||
<div class="text-sm text-black">{{ week1_tue_zone | default('Zone 2') }}</div>
|
||||
<div class="text-sm text-black">{{ week1_tue_duration | default('45 mins') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Wednesday -->
|
||||
<div class="bg-cyan-300 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Wednesday</div>
|
||||
<div class="text-sm text-black">{{ week1_wed_zone | default('Zone 3') }}</div>
|
||||
<div class="text-sm text-black">{{ week1_wed_duration1 | default('10mins On') }}</div>
|
||||
<div class="text-sm text-black">{{ week1_wed_duration2 | default('8mins Rest') }}</div>
|
||||
<div class="text-sm text-black">{{ week1_wed_sets | default('x2') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Thursday -->
|
||||
<div class="bg-gray-200 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Thursday</div>
|
||||
<div class="text-sm text-black">{{ week1_thu_content | default('') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Friday -->
|
||||
<div class="bg-cyan-300 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Friday</div>
|
||||
<div class="text-sm text-black">{{ week1_fri_zone | default('Zone 2') }}</div>
|
||||
<div class="text-sm text-black">{{ week1_fri_duration | default('45 mins') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Saturday -->
|
||||
<div class="bg-gray-200 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Saturday</div>
|
||||
<div class="text-sm text-black">{{ week1_sat_content | default('') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Sunday -->
|
||||
<div class="bg-gray-200 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Sunday</div>
|
||||
<div class="text-sm text-black">{{ week1_sun_content | default('') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Training Week Guidelines Section -->
|
||||
<div class="mb-6">
|
||||
<h2 class="text-2xl font-bold text-black mb-6 text-center">Training Week Guidelines</h2>
|
||||
|
||||
<!-- Guidelines Grid -->
|
||||
<div class="grid grid-cols-5 gap-4 mb-6">
|
||||
<!-- Zone 1 -->
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-black mb-3">Zone 1</h3>
|
||||
<ul class="text-sm text-black space-y-2 list-disc list-inside">
|
||||
<li>Zone 1 training is low intensity, for active recovery.</li>
|
||||
<li>It can be done daily or even consecutively, depending on fitness, volume, and health.</li>
|
||||
</ul>
|
||||
<!-- Week 2 -->
|
||||
<div class="mb-6">
|
||||
<div class="grid grid-cols-7 gap-2">
|
||||
<!-- Monday -->
|
||||
<div class="bg-cyan-300 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Monday</div>
|
||||
<div class="text-sm text-black">{{ week2_mon_zone | default('Zone 2') }}</div>
|
||||
<div class="text-sm text-black">{{ week2_mon_duration | default('50 mins') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Tuesday -->
|
||||
<div class="bg-cyan-300 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Tuesday</div>
|
||||
<div class="text-sm text-black">{{ week2_tue_zone | default('Zone 2') }}</div>
|
||||
<div class="text-sm text-black">{{ week2_tue_duration | default('50 mins') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Wednesday -->
|
||||
<div class="bg-cyan-300 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Wednesday</div>
|
||||
<div class="text-sm text-black">{{ week2_wed_zone | default('Zone 3') }}</div>
|
||||
<div class="text-sm text-black">{{ week2_wed_duration1 | default('10mins On') }}</div>
|
||||
<div class="text-sm text-black">{{ week2_wed_duration2 | default('6mins Rest') }}</div>
|
||||
<div class="text-sm text-black">{{ week2_wed_sets | default('x2') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Thursday -->
|
||||
<div class="bg-gray-200 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Thursday</div>
|
||||
<div class="text-sm text-black">{{ week2_thu_content | default('') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Friday -->
|
||||
<div class="bg-cyan-300 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Friday</div>
|
||||
<div class="text-sm text-black">{{ week2_fri_zone | default('Zone 2') }}</div>
|
||||
<div class="text-sm text-black">{{ week2_fri_duration | default('50 mins') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Saturday -->
|
||||
<div class="bg-gray-200 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Saturday</div>
|
||||
<div class="text-sm text-black">{{ week2_sat_content | default('') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Sunday -->
|
||||
<div class="bg-gray-200 p-3 text-center rounded-lg">
|
||||
<div class="font-bold text-black mb-1">Sunday</div>
|
||||
<div class="text-sm text-black">{{ week2_sun_content | default('') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Zone 2 -->
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-black mb-3">Zone 2</h3>
|
||||
<ul class="text-sm text-black space-y-2 list-disc list-inside">
|
||||
<li>Zone 2 training can be done on consecutive or daily basis with moderate sessions.</li>
|
||||
<li>Can be steady state or interval sessions.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Zone 3 -->
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-black mb-3">Zone 3</h3>
|
||||
<ul class="text-sm text-black space-y-2 list-disc list-inside">
|
||||
<li>Zone 3 training can be done 1-5 times per week.</li>
|
||||
<li>Wait 24 to 48 hours between sessions for adequate recovery.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Zone 4 -->
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-black mb-3">Zone 4</h3>
|
||||
<ul class="text-sm text-black space-y-2 list-disc list-inside">
|
||||
<li>Zone 4 training: 1-4 times per week.</li>
|
||||
<li>Wait 24 to 48 hours between intense sessions for recovery.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Zone 5 -->
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-black mb-3">Zone 5</h3>
|
||||
<ul class="text-sm text-black space-y-2 list-disc list-inside">
|
||||
<li>Zone 5 training: 1-2 times per week.</li>
|
||||
<li>Wait 48 hours between sessions for recovery.</li>
|
||||
<li>Zone 5 increases VO2 max and endurance at VO2 max.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Important Note -->
|
||||
<div class="text-center">
|
||||
<p class="text-lg font-bold text-black italic">Zone 3, 4, 5 can be combined with Zone 1 or 2 - the higher zone should be done first!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Section -->
|
||||
</div>
|
||||
|
||||
+132
-54
@@ -3,68 +3,146 @@
|
||||
<!-- Main Content -->
|
||||
<div class="px-8 py-6">
|
||||
<!-- Page Title -->
|
||||
<h1 class="text-3xl font-bold text-black mb-8">Next Steps:</h1>
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-3xl font-bold text-black mb-2">Training Week</h1>
|
||||
<p class="text-gray-600 italic">(To be filled out by your trainer)</p>
|
||||
</div>
|
||||
|
||||
<!-- Calorie Tracking Section -->
|
||||
<!-- First Training Week Grid -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-bold text-black mb-4">Calorie Tracking</h2>
|
||||
<ul class="text-black space-y-2 list-disc list-inside ml-4">
|
||||
<li>Download and create an account with My Fitness Pal (or preferred nutrition tracker)</li>
|
||||
<li>Fill out the "My Profile" section with your goals (ie: height, weight, target weight)
|
||||
<ul class="mt-2 ml-6 space-y-1 list-disc list-inside">
|
||||
<li>Input your Macros</li>
|
||||
<li>Click the three dots on the bottom right corner</li>
|
||||
<li>Click "Goals"</li>
|
||||
<li>Click "Calorie, Carbs, Protein and Fat Goals" under the Nutrition Goals</li>
|
||||
<li>Set the Calories, Carbs, Protein, and Fat to the recommended macro outlined above.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Once completed fill out your food intake from each meal on the main page</li>
|
||||
</ul>
|
||||
<div class="grid grid-cols-7 gap-2 mb-4">
|
||||
<!-- Week 1 Headers -->
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Monday</div>
|
||||
</div>
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Tuesday</div>
|
||||
</div>
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Wednesday</div>
|
||||
</div>
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Thursday</div>
|
||||
</div>
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Friday</div>
|
||||
</div>
|
||||
<div class="bg-gray-200 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Saturday</div>
|
||||
</div>
|
||||
<div class="bg-gray-200 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Sunday</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recommendation Note -->
|
||||
<div class="mt-6 text-center">
|
||||
<p class="text-black italic font-semibold">It's highly recommended to purchase a weight and food scale for more accurate results.</p>
|
||||
<!-- Week 1 Content Boxes -->
|
||||
<div class="grid grid-cols-7 gap-2 mb-8">
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Daily Tasks Section -->
|
||||
<!-- Second Training Week Grid -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-bold text-black mb-4">Daily Tasks</h2>
|
||||
<ul class="text-black space-y-2 list-disc list-inside ml-4">
|
||||
<li>Weigh yourself in the morning, after your first bowel movement, and naked</li>
|
||||
<li>Log your weight into your my fitness pal app</li>
|
||||
<li>Track calories in grams - FOLLOW YOUR PERSONAL RECOMMENDATIONS.</li>
|
||||
<li>Log in a diary:
|
||||
<ul class="mt-2 ml-6 space-y-1 list-disc list-inside">
|
||||
<li>Log any additional prescribed recommendation (i.e breath work)</li>
|
||||
<li>Complete the prescribed training recommendations (i.e Zone 2 Training)</li>
|
||||
<li>Log additional physical activity (i.e Monday - Strength Training 1 hour)</li>
|
||||
<div class="grid grid-cols-7 gap-2 mb-4">
|
||||
<!-- Week 2 Headers -->
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Monday</div>
|
||||
</div>
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Tuesday</div>
|
||||
</div>
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Wednesday</div>
|
||||
</div>
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Thursday</div>
|
||||
</div>
|
||||
<div class="bg-cyan-300 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Friday</div>
|
||||
</div>
|
||||
<div class="bg-gray-200 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Saturday</div>
|
||||
</div>
|
||||
<div class="bg-gray-200 p-2 text-center rounded-lg">
|
||||
<div class="font-bold text-black">Sunday</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Week 2 Content Boxes -->
|
||||
<div class="grid grid-cols-7 gap-2 mb-8">
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
<div class="bg-gray-100 border-2 border-gray-300 h-32 rounded-lg"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Training Week Guidelines Section -->
|
||||
<div class="mb-6">
|
||||
<h2 class="text-2xl font-bold text-black mb-6 text-center">Training Week Guidelines</h2>
|
||||
|
||||
<!-- Guidelines Grid -->
|
||||
<div class="grid grid-cols-5 gap-4 mb-6">
|
||||
<!-- Zone 1 -->
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-black mb-3">Zone 1</h3>
|
||||
<ul class="text-sm text-black space-y-2 list-disc list-inside">
|
||||
<li>Zone 1 training is low intensity, for active recovery.</li>
|
||||
<li>It can be done daily or even consecutively, depending on fitness, volume, and health.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Two weeks after Appointment Section -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-bold text-black mb-4">Two weeks after Appointment</h2>
|
||||
<ul class="text-black space-y-2 list-disc list-inside ml-4">
|
||||
<li>Should you find the macronutrient breakdown difficult to follow, reach out to us to discuss a change within your caloric parameters</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Contact Information -->
|
||||
<div class="mb-12 text-center">
|
||||
<p class="text-lg font-bold text-black">Should you have any questions or concerns please contact us!</p>
|
||||
</div>
|
||||
|
||||
<!-- Recommended Next Testing Date -->
|
||||
<div class="mb-8 text-center">
|
||||
<h2 class="text-2xl font-bold text-black">
|
||||
<span class="underline">Recommended Next Testing Date:</span>
|
||||
<span class="ml-2">October 2025</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- Zone 2 -->
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-black mb-3">Zone 2</h3>
|
||||
<ul class="text-sm text-black space-y-2 list-disc list-inside">
|
||||
<li>Zone 2 training can be done on consecutive or daily basis with moderate sessions.</li>
|
||||
<li>Can be steady state or interval sessions.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Zone 3 -->
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-black mb-3">Zone 3</h3>
|
||||
<ul class="text-sm text-black space-y-2 list-disc list-inside">
|
||||
<li>Zone 3 training can be done 1-5 times per week.</li>
|
||||
<li>Wait 24 to 48 hours between sessions for adequate recovery.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Zone 4 -->
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-black mb-3">Zone 4</h3>
|
||||
<ul class="text-sm text-black space-y-2 list-disc list-inside">
|
||||
<li>Zone 4 training: 1-4 times per week.</li>
|
||||
<li>Wait 24 to 48 hours between intense sessions for recovery.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Zone 5 -->
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-black mb-3">Zone 5</h3>
|
||||
<ul class="text-sm text-black space-y-2 list-disc list-inside">
|
||||
<li>Zone 5 training: 1-2 times per week.</li>
|
||||
<li>Wait 48 hours between sessions for recovery.</li>
|
||||
<li>Zone 5 increases VO2 max and endurance at VO2 max.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Important Note -->
|
||||
<div class="text-center">
|
||||
<p class="text-lg font-bold text-black italic">Zone 3, 4, 5 can be combined with Zone 1 or 2 - the higher zone should be done first!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
+57
-44
@@ -1,56 +1,69 @@
|
||||
<div class="w-full page bg-white">
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="px-6">
|
||||
<div class="px-8 py-6">
|
||||
<!-- Page Title -->
|
||||
<h1 class="text-2xl font-bold text-black mb-4">Glossary</h1>
|
||||
<h1 class="text-3xl font-bold text-black mb-8">Next Steps:</h1>
|
||||
|
||||
<!-- Body Fat Percentage -->
|
||||
<div class="mb-3">
|
||||
<h2 class="text-base font-bold text-black mb-1">Body Fat Percentage:</h2>
|
||||
<p class="text-xs text-black leading-snug">The percentage of your overall body weight that is composed of fat cells. Body fat percentage can be reduced by either losing weight from fat mass, while maintaining lean mass, or maintaining fat mass while increasing lean mass.</p>
|
||||
</div>
|
||||
|
||||
<!-- Metabolic Rate -->
|
||||
<div class="mb-3">
|
||||
<h2 class="text-base font-bold text-black mb-1">Metabolic Rate:</h2>
|
||||
<p class="text-xs text-black leading-snug">Metabolic Rate measures the number of calories your body burns for basic functions and movement, based on factors like weight, age, gender, and height. A higher metabolic rate helps prevent weight gain and supports weight loss by ensuring you burn enough calories. Tracking metabolic rate is key for managing weight and preventing conditions linked to metabolic dysfunction. Positive influences include resistance exercise, proper sleep, and adequate protein, while negative factors include extreme dieting, yo-yo dieting, and excessive cardio. Improving it involves resistance training and optimal nutrition.</p>
|
||||
</div>
|
||||
|
||||
<!-- Fuel Source -->
|
||||
<div class="mb-3">
|
||||
<h2 class="text-base font-bold text-black mb-1">Fuel Source:</h2>
|
||||
<p class="text-xs text-black leading-snug mb-1">Fat-burning efficiency measures your cells' ability to use fat as fuel, reflecting mitochondrial and cellular health. It indicates how well your body balances fat and carbohydrate usage to support energy needs, assessed by analyzing oxygen and carbon dioxide in your breath. High fat-burning efficiency suggests strong metabolic and mitochondrial function, linked to better weight management and longevity.</p>
|
||||
<p class="text-xs text-black leading-snug">To improve fat-burning efficiency, focus on Zone 2 endurance training and potentially intermittent fasting to enhance oxygen absorption and cellular function. Zone 5 interval training will also help improve fat burning mitochondrial density and capillarization. Factors that reduce fat burning ability include diets high in processed foods, alcohol, and large meals before bed. Conditions related to metabolic stress also hinder fat burning abilities.</p>
|
||||
</div>
|
||||
|
||||
<!-- NEAT -->
|
||||
<div class="mb-3">
|
||||
<h2 class="text-base font-bold text-black mb-1">NEAT (Non-Exercise Activity Thermogenesis)</h2>
|
||||
<p class="text-xs text-black leading-snug">refers to the energy expended for all activities that are not deliberate exercise or structured physical activity. This includes daily movements such as walking, fidgeting, standing, cleaning, typing, and even simple tasks like cooking or shopping. NEAT contributes significantly to the total caloric expenditure and plays a key role in maintaining body weight and overall energy balance. It varies widely among individuals, depending on lifestyle, occupation, and habits.</p>
|
||||
</div>
|
||||
|
||||
<!-- Spirometry -->
|
||||
<div class="mb-3">
|
||||
<h2 class="text-base font-bold text-black mb-1">Spirometry:</h2>
|
||||
<p class="text-xs text-black leading-snug mb-1">Spirometry is a diagnostic device used to provide objective measurements of lung volumes and capacities. Lung function is crucial for oxygen delivery during physical activity, and comparing spirometry results to expected values can highlight any potential limitations to performance.</p>
|
||||
<p class="text-xs text-black leading-snug mb-1">"From a Performance standpoint, it is essential in making informed training recommendations related to respiratory health to optimize endurance performance and metabolic health."</p>
|
||||
|
||||
<!-- Spirometry Sub-definitions -->
|
||||
<ul class="text-xs text-black space-y-1 list-disc list-inside ml-3">
|
||||
<li><strong>FEV1:</strong> Forced Expiratory Volume - the total amount of air expelled in the first second.</li>
|
||||
<li><strong>FVC:</strong> Forced Vital Capacity - the maximum amount of air exhaled in one breath after a maximum inhalation</li>
|
||||
<li><strong>FEV1/FVC:</strong> Calculated ratio used in the diagnosis of obstructive & restrictive lung disease.</li>
|
||||
<!-- Calorie Tracking Section -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-bold text-black mb-4">Calorie Tracking</h2>
|
||||
<ul class="text-black space-y-2 list-disc list-inside ml-4">
|
||||
<li>Download and create an account with My Fitness Pal (or preferred nutrition tracker)</li>
|
||||
<li>Fill out the "My Profile" section with your goals (ie: height, weight, target weight)
|
||||
<ul class="mt-2 ml-6 space-y-1 list-disc list-inside">
|
||||
<li>Input your Macros</li>
|
||||
<li>Click the three dots on the bottom right corner</li>
|
||||
<li>Click "Goals"</li>
|
||||
<li>Click "Calorie, Carbs, Protein and Fat Goals" under the Nutrition Goals</li>
|
||||
<li>Set the Calories, Carbs, Protein, and Fat to the recommended macro outlined above.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Once completed fill out your food intake from each meal on the main page</li>
|
||||
</ul>
|
||||
|
||||
<p class="text-xs text-black leading-snug mt-1">By comparing these measurements to expected values based on age, gender, height and ethnicity, healthcare professionals can diagnose a range of lung conditions such as asthma, COPD, restrictive lung diseases, and more.</p>
|
||||
<!-- Recommendation Note -->
|
||||
<div class="mt-6 text-center">
|
||||
<p class="text-black italic font-semibold">It's highly recommended to purchase a weight and food scale for more accurate results.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VO2 max -->
|
||||
<div>
|
||||
<h2 class="text-base font-bold text-black mb-1">VO2 max:</h2>
|
||||
<p class="text-xs text-black leading-snug mb-1">VO2 Max, or maximal oxygen consumption serves as a valuable indicator of overall fitness, cardiovascular health, and endurance capacity. VO2 max reflects the efficiency of your heart lung system in pumping oxygen-rich blood to working muscles. A higher VO2 max indicates a stronger cardiovascular system, which is associated with a reduced risk of heart disease and other cardiovascular issues.</p>
|
||||
<p class="text-xs text-black leading-snug">Understanding and training to increase your VO2 max can contribute to enhanced physical performance, longevity and well-being.</p>
|
||||
<!-- Daily Tasks Section -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-bold text-black mb-4">Daily Tasks</h2>
|
||||
<ul class="text-black space-y-2 list-disc list-inside ml-4">
|
||||
<li>Weigh yourself in the morning, after your first bowel movement, and naked</li>
|
||||
<li>Log your weight into your my fitness pal app</li>
|
||||
<li>Track calories in grams - FOLLOW YOUR PERSONAL RECOMMENDATIONS.</li>
|
||||
<li>Log in a diary:
|
||||
<ul class="mt-2 ml-6 space-y-1 list-disc list-inside">
|
||||
<li>Log any additional prescribed recommendation (i.e breath work)</li>
|
||||
<li>Complete the prescribed training recommendations (i.e Zone 2 Training)</li>
|
||||
<li>Log additional physical activity (i.e Monday - Strength Training 1 hour)</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Two weeks after Appointment Section -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-bold text-black mb-4">Two weeks after Appointment</h2>
|
||||
<ul class="text-black space-y-2 list-disc list-inside ml-4">
|
||||
<li>Should you find the macronutrient breakdown difficult to follow, reach out to us to discuss a change within your caloric parameters</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Contact Information -->
|
||||
<div class="mb-12 text-center">
|
||||
<p class="text-lg font-bold text-black">Should you have any questions or concerns please contact us!</p>
|
||||
</div>
|
||||
|
||||
<!-- Recommended Next Testing Date -->
|
||||
<div class="mb-8 text-center">
|
||||
<h2 class="text-2xl font-bold text-black">
|
||||
<span class="underline">Recommended Next Testing Date:</span>
|
||||
<span class="ml-2">October 2025</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+53
-84
@@ -1,88 +1,57 @@
|
||||
<!-- Header -->
|
||||
<div class="w-full page">
|
||||
<div class="w-full page bg-white">
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="p-4 text-sm">
|
||||
<h1 class="text-2xl font-bold mb-4">Glossary</h1>
|
||||
|
||||
<div class="space-y-3 leading-tight">
|
||||
<!-- Peak VT -->
|
||||
<div>
|
||||
<p class="font-semibold">Peak VT:</p>
|
||||
<p class="mb-1">
|
||||
Peak Volume of air moved throughout the test.
|
||||
</p>
|
||||
<p class="mb-1">
|
||||
Respiratory Capability Limitations that can be found include:
|
||||
</p>
|
||||
<ul class="list-disc ml-4 space-y-0">
|
||||
<li>
|
||||
<strong>Endurance:</strong> Normal capacity, but cannot maintain their VT over time.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Strength/Power:</strong> Normal capacity, but peak VT is not 75-85% of their FEV1 despite FEV1 being normal
|
||||
</li>
|
||||
<li>
|
||||
<strong>Coordination (Hyper/Hypo-Ventilation):</strong> Normal capacity, but uses low volumes +/- high BFs at lower intensities. A breathing coordination limitation can also be identified by the loss of volume at higher intensities, which are then recovered upon recovery/stop of activity.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- VO2 Pulse -->
|
||||
<div>
|
||||
<p class="font-semibold">VO2 Pulse:</p>
|
||||
<p>
|
||||
VO2 Pulse refers to the relationship between oxygen consumption (VO2) and heart rate (HR) during exercise. This measure gives insight into how efficiently the body is using oxygen in relation to the heart's output. A higher VO2 Pulse suggests that an individual is able to deliver oxygen more efficiently to the muscles with each heartbeat.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- VO2 Breath -->
|
||||
<div>
|
||||
<p class="font-semibold">VO2 Breath:</p>
|
||||
<p>
|
||||
VO2 Breath refers to the amount of oxygen consumed per breath during exercise, which indicates how effectively the body delivers oxygen to the bloodstream through the lungs with each breath. A more efficient VO2 Breath means the body requires less effort to obtain the same amount of oxygen, indicating better respiratory efficiency and oxygen utilization.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Carb & Fat Crossover -->
|
||||
<div>
|
||||
<p class="font-semibold">Carb & Fat Crossover:</p>
|
||||
<p class="mb-1">
|
||||
The point during exercise at which the body shifts its predominant fuel source from fats to carbohydrates. This transition typically occurs as exercise intensity increases, and marks the transition from Zone 2 into Zone 3. As exercise intensity increases, the body starts to rely more on carbohydrates because they provide faster energy.
|
||||
</p>
|
||||
<p>
|
||||
Endurance training (e.g., long, steady-state cardio within Zones 1 & 2) increases the body's ability to burn fat efficiently at higher intensities, shifting the crossover point to a faster speed, or higher heart rate/intensity. Because fat stores are much larger and can provide a steady stream of energy for prolonged periods, a higher CHO/FAT crossover can help delay fatigue, which is especially beneficial in longer-duration events, where carbohydrate depletion can lead to a significant drop in performance.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Cardiovascular Recovery -->
|
||||
<div>
|
||||
<p class="font-semibold">Cardiovascular Recovery:</p>
|
||||
<p>
|
||||
The percentage your heart rate drops within the first minute of the inactive recovery phase in relation to the lowest heart rate recorded prior to the start of the test.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Metabolic (CO2) Recovery -->
|
||||
<div>
|
||||
<p class="font-semibold">Metabolic (CO2) Recovery:</p>
|
||||
<p class="mb-1">
|
||||
The percentage that your VCO2 levels (amount of CO2 you are exhaling) drop within the first 1.5 minutes of the inactive recovery phase in relation to the lowest VCO2 recorded prior to the start of the test.
|
||||
</p>
|
||||
<p>
|
||||
refers to the rate at which the body clears carbon dioxide (CO2) after exercise, reflecting the efficiency of the cardiovascular and respiratory systems in returning CO2 levels to baseline. A faster VCO2 recovery indicates effective management of metabolic byproducts, signaling a healthier metabolic system and lower risk of metabolic disorders.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Breath Frequency Recovery -->
|
||||
<div>
|
||||
<p class="font-semibold">Breath Frequency Recovery:</p>
|
||||
<p>
|
||||
Refers to the speed at which the body returns to a normal breathing rate after physical exertion. Faster breath frequency recovery indicates a well-conditioned cardiovascular and respiratory system, allowing the body to efficiently regulate oxygen and CO2 levels. It supports better endurance, faster recovery between intervals, and the ability to sustain higher performance during repeated efforts or prolonged activity. Additionally, a quick return to baseline signals that the autonomic nervous system is functioning well, reducing stress on the body and promoting more efficient recovery. This also reflects a healthier metabolic system, better management of metabolic byproducts like CO2, and a lower risk of chronic conditions.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Main Content -->
|
||||
<div class="px-6">
|
||||
<!-- Page Title -->
|
||||
<h1 class="text-2xl font-bold text-black mb-4">Glossary</h1>
|
||||
|
||||
<!-- Body Fat Percentage -->
|
||||
<div class="mb-3">
|
||||
<h2 class="text-base font-bold text-black mb-1">Body Fat Percentage:</h2>
|
||||
<p class="text-xs text-black leading-snug">The percentage of your overall body weight that is composed of fat cells. Body fat percentage can be reduced by either losing weight from fat mass, while maintaining lean mass, or maintaining fat mass while increasing lean mass.</p>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<!-- Metabolic Rate -->
|
||||
<div class="mb-3">
|
||||
<h2 class="text-base font-bold text-black mb-1">Metabolic Rate:</h2>
|
||||
<p class="text-xs text-black leading-snug">Metabolic Rate measures the number of calories your body burns for basic functions and movement, based on factors like weight, age, gender, and height. A higher metabolic rate helps prevent weight gain and supports weight loss by ensuring you burn enough calories. Tracking metabolic rate is key for managing weight and preventing conditions linked to metabolic dysfunction. Positive influences include resistance exercise, proper sleep, and adequate protein, while negative factors include extreme dieting, yo-yo dieting, and excessive cardio. Improving it involves resistance training and optimal nutrition.</p>
|
||||
</div>
|
||||
|
||||
<!-- Fuel Source -->
|
||||
<div class="mb-3">
|
||||
<h2 class="text-base font-bold text-black mb-1">Fuel Source:</h2>
|
||||
<p class="text-xs text-black leading-snug mb-1">Fat-burning efficiency measures your cells' ability to use fat as fuel, reflecting mitochondrial and cellular health. It indicates how well your body balances fat and carbohydrate usage to support energy needs, assessed by analyzing oxygen and carbon dioxide in your breath. High fat-burning efficiency suggests strong metabolic and mitochondrial function, linked to better weight management and longevity.</p>
|
||||
<p class="text-xs text-black leading-snug">To improve fat-burning efficiency, focus on Zone 2 endurance training and potentially intermittent fasting to enhance oxygen absorption and cellular function. Zone 5 interval training will also help improve fat burning mitochondrial density and capillarization. Factors that reduce fat burning ability include diets high in processed foods, alcohol, and large meals before bed. Conditions related to metabolic stress also hinder fat burning abilities.</p>
|
||||
</div>
|
||||
|
||||
<!-- NEAT -->
|
||||
<div class="mb-3">
|
||||
<h2 class="text-base font-bold text-black mb-1">NEAT (Non-Exercise Activity Thermogenesis)</h2>
|
||||
<p class="text-xs text-black leading-snug">refers to the energy expended for all activities that are not deliberate exercise or structured physical activity. This includes daily movements such as walking, fidgeting, standing, cleaning, typing, and even simple tasks like cooking or shopping. NEAT contributes significantly to the total caloric expenditure and plays a key role in maintaining body weight and overall energy balance. It varies widely among individuals, depending on lifestyle, occupation, and habits.</p>
|
||||
</div>
|
||||
|
||||
<!-- Spirometry -->
|
||||
<div class="mb-3">
|
||||
<h2 class="text-base font-bold text-black mb-1">Spirometry:</h2>
|
||||
<p class="text-xs text-black leading-snug mb-1">Spirometry is a diagnostic device used to provide objective measurements of lung volumes and capacities. Lung function is crucial for oxygen delivery during physical activity, and comparing spirometry results to expected values can highlight any potential limitations to performance.</p>
|
||||
<p class="text-xs text-black leading-snug mb-1">"From a Performance standpoint, it is essential in making informed training recommendations related to respiratory health to optimize endurance performance and metabolic health."</p>
|
||||
|
||||
<!-- Spirometry Sub-definitions -->
|
||||
<ul class="text-xs text-black space-y-1 list-disc list-inside ml-3">
|
||||
<li><strong>FEV1:</strong> Forced Expiratory Volume - the total amount of air expelled in the first second.</li>
|
||||
<li><strong>FVC:</strong> Forced Vital Capacity - the maximum amount of air exhaled in one breath after a maximum inhalation</li>
|
||||
<li><strong>FEV1/FVC:</strong> Calculated ratio used in the diagnosis of obstructive & restrictive lung disease.</li>
|
||||
</ul>
|
||||
|
||||
<p class="text-xs text-black leading-snug mt-1">By comparing these measurements to expected values based on age, gender, height and ethnicity, healthcare professionals can diagnose a range of lung conditions such as asthma, COPD, restrictive lung diseases, and more.</p>
|
||||
</div>
|
||||
|
||||
<!-- VO2 max -->
|
||||
<div>
|
||||
<h2 class="text-base font-bold text-black mb-1">VO2 max:</h2>
|
||||
<p class="text-xs text-black leading-snug mb-1">VO2 Max, or maximal oxygen consumption serves as a valuable indicator of overall fitness, cardiovascular health, and endurance capacity. VO2 max reflects the efficiency of your heart lung system in pumping oxygen-rich blood to working muscles. A higher VO2 max indicates a stronger cardiovascular system, which is associated with a reduced risk of heart disease and other cardiovascular issues.</p>
|
||||
<p class="text-xs text-black leading-snug">Understanding and training to increase your VO2 max can contribute to enhanced physical performance, longevity and well-being.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
+81
-31
@@ -1,38 +1,88 @@
|
||||
<!-- Header -->
|
||||
<div class="w-full page">
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="p-8">
|
||||
<h1 class="text-4xl font-bold mb-8">Glossary</h1>
|
||||
<div class="p-4 text-sm">
|
||||
<h1 class="text-2xl font-bold mb-4">Glossary</h1>
|
||||
|
||||
<!-- Local Muscle Activity/SMO2 Definition -->
|
||||
<div class="mb-12 text-sm leading-relaxed">
|
||||
<p class="font-semibold mb-2">Local Muscle Activity/SMO2:</p>
|
||||
<p>
|
||||
SmO2 testing is a valuable tool for understanding how muscles
|
||||
respond to various physiological stressors and how to fine-tune
|
||||
training, nutrition and hydration accordingly. Monitoring
|
||||
changes in tissue oxygen saturation and utilization helps
|
||||
identify an individual's optimal intensity to work at, as well
|
||||
as how well they recover between bouts of intensity. This can
|
||||
help optimize training to improve performance, prevent
|
||||
overtraining, and tailor strategies for better results.
|
||||
</p>
|
||||
<p>
|
||||
During competitions, athletes can also use SmO2 data to pace
|
||||
themselves effectively. Adjusting intensity based on muscle
|
||||
oxygenation can help prevent premature fatigue and optimize race
|
||||
outcomes
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-3 leading-tight">
|
||||
<!-- Peak VT -->
|
||||
<div>
|
||||
<p class="font-semibold">Peak VT:</p>
|
||||
<p class="mb-1">
|
||||
Peak Volume of air moved throughout the test.
|
||||
</p>
|
||||
<p class="mb-1">
|
||||
Respiratory Capability Limitations that can be found include:
|
||||
</p>
|
||||
<ul class="list-disc ml-4 space-y-0">
|
||||
<li>
|
||||
<strong>Endurance:</strong> Normal capacity, but cannot maintain their VT over time.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Strength/Power:</strong> Normal capacity, but peak VT is not 75-85% of their FEV1 despite FEV1 being normal
|
||||
</li>
|
||||
<li>
|
||||
<strong>Coordination (Hyper/Hypo-Ventilation):</strong> Normal capacity, but uses low volumes +/- high BFs at lower intensities. A breathing coordination limitation can also be identified by the loss of volume at higher intensities, which are then recovered upon recovery/stop of activity.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="w-full max-w-5xl">
|
||||
<h1 class="text-2xl font-bold mb-4 text-center">
|
||||
Body Fat Percent Master Chart
|
||||
</h1>
|
||||
<img
|
||||
src="data:image/png;base64,{{ body_fat_percentage_chart }}"
|
||||
alt="Body Fat Percentage"
|
||||
class="w-full h-auto object-contain chart-large"
|
||||
/>
|
||||
<!-- VO2 Pulse -->
|
||||
<div>
|
||||
<p class="font-semibold">VO2 Pulse:</p>
|
||||
<p>
|
||||
VO2 Pulse refers to the relationship between oxygen consumption (VO2) and heart rate (HR) during exercise. This measure gives insight into how efficiently the body is using oxygen in relation to the heart's output. A higher VO2 Pulse suggests that an individual is able to deliver oxygen more efficiently to the muscles with each heartbeat.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- VO2 Breath -->
|
||||
<div>
|
||||
<p class="font-semibold">VO2 Breath:</p>
|
||||
<p>
|
||||
VO2 Breath refers to the amount of oxygen consumed per breath during exercise, which indicates how effectively the body delivers oxygen to the bloodstream through the lungs with each breath. A more efficient VO2 Breath means the body requires less effort to obtain the same amount of oxygen, indicating better respiratory efficiency and oxygen utilization.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Carb & Fat Crossover -->
|
||||
<div>
|
||||
<p class="font-semibold">Carb & Fat Crossover:</p>
|
||||
<p class="mb-1">
|
||||
The point during exercise at which the body shifts its predominant fuel source from fats to carbohydrates. This transition typically occurs as exercise intensity increases, and marks the transition from Zone 2 into Zone 3. As exercise intensity increases, the body starts to rely more on carbohydrates because they provide faster energy.
|
||||
</p>
|
||||
<p>
|
||||
Endurance training (e.g., long, steady-state cardio within Zones 1 & 2) increases the body's ability to burn fat efficiently at higher intensities, shifting the crossover point to a faster speed, or higher heart rate/intensity. Because fat stores are much larger and can provide a steady stream of energy for prolonged periods, a higher CHO/FAT crossover can help delay fatigue, which is especially beneficial in longer-duration events, where carbohydrate depletion can lead to a significant drop in performance.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Cardiovascular Recovery -->
|
||||
<div>
|
||||
<p class="font-semibold">Cardiovascular Recovery:</p>
|
||||
<p>
|
||||
The percentage your heart rate drops within the first minute of the inactive recovery phase in relation to the lowest heart rate recorded prior to the start of the test.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Metabolic (CO2) Recovery -->
|
||||
<div>
|
||||
<p class="font-semibold">Metabolic (CO2) Recovery:</p>
|
||||
<p class="mb-1">
|
||||
The percentage that your VCO2 levels (amount of CO2 you are exhaling) drop within the first 1.5 minutes of the inactive recovery phase in relation to the lowest VCO2 recorded prior to the start of the test.
|
||||
</p>
|
||||
<p>
|
||||
refers to the rate at which the body clears carbon dioxide (CO2) after exercise, reflecting the efficiency of the cardiovascular and respiratory systems in returning CO2 levels to baseline. A faster VCO2 recovery indicates effective management of metabolic byproducts, signaling a healthier metabolic system and lower risk of metabolic disorders.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Breath Frequency Recovery -->
|
||||
<div>
|
||||
<p class="font-semibold">Breath Frequency Recovery:</p>
|
||||
<p>
|
||||
Refers to the speed at which the body returns to a normal breathing rate after physical exertion. Faster breath frequency recovery indicates a well-conditioned cardiovascular and respiratory system, allowing the body to efficiently regulate oxygen and CO2 levels. It supports better endurance, faster recovery between intervals, and the ability to sustain higher performance during repeated efforts or prolonged activity. Additionally, a quick return to baseline signals that the autonomic nervous system is functioning well, reducing stress on the body and promoting more efficient recovery. This also reflects a healthier metabolic system, better management of metabolic byproducts like CO2, and a lower risk of chronic conditions.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
</div>
|
||||
|
||||
+32
-808
@@ -1,814 +1,38 @@
|
||||
<div class="w-full page bg-white">
|
||||
|
||||
<div class="w-full page">
|
||||
<!-- Main Content -->
|
||||
<div class="p-6">
|
||||
<h1 class="text-3xl font-bold mb-6">Glossary</h1>
|
||||
<div class="p-8">
|
||||
<h1 class="text-4xl font-bold mb-8">Glossary</h1>
|
||||
|
||||
<!-- Resting Heart Rate Section -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-bold mb-4 text-center">
|
||||
Resting Heart Rate
|
||||
</h2>
|
||||
|
||||
<!-- Male Table -->
|
||||
<div class="mb-4">
|
||||
<table
|
||||
class="w-full border-collapse border border-gray-300 text-xs"
|
||||
>
|
||||
<thead>
|
||||
<tr class="bg-cyan-200">
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Age (M)
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Poor
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Below Average
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Average
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Above Average
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Good
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Excellent
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Athlete
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
18-25
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
85bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
76-84bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
74-78bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
70-73bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
66-69bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
61-65bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
60-60bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
26-35
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
83bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
77-82bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
73-76bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
69-72bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
65-68bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
60-64bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
55-59bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
36-45
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
85bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
79-84bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
74-78bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
70-73bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
65-69bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
60-64bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
55-59bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
46-55
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
84bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
76-83bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
73-77bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
70-72bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
66-69bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
61-65bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
56-60bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
56-65
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
85bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
78-84bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
74-77bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
70-73bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
65-69bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
60-64bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
50-59bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
65+
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
84bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
77-83bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
73-76bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
70-73bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
65-69bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
60-64bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
55-59bpm
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Female Table -->
|
||||
<div class="mb-4">
|
||||
<table
|
||||
class="w-full border-collapse border border-gray-300 text-xs"
|
||||
>
|
||||
<thead>
|
||||
<tr class="bg-cyan-200">
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Age (F)
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Poor
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Below Average
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Average
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Above Average
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Good
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Excellent
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Athlete
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
18-25
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
81bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
74-81bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
73-78bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
66-69bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
62-65bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
56-61bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
50-55bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
26-35
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
82bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
75-81bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
71-74bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
66-70bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
62-65bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
55-61bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
54-54bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
36-45
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
83bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
76-82bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
71-75bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
67-70bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
63-66bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
57-62bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
47-56bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
46-55
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
84bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
77-83bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
72-76bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
68-71bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
64-67bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
58-63bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
49-57bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
56-65
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
82bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
76-81bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
72-75bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
68-71bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
62-67bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
57-61bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
51-56bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
65+
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
80bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
74-79bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
70-73bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
66-69bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
62-65bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
56-61bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
52-55bpm
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Local Muscle Activity/SMO2 Definition -->
|
||||
<div class="mb-12 text-sm leading-relaxed">
|
||||
<p class="font-semibold mb-2">Local Muscle Activity/SMO2:</p>
|
||||
<p>
|
||||
SmO2 testing is a valuable tool for understanding how muscles
|
||||
respond to various physiological stressors and how to fine-tune
|
||||
training, nutrition and hydration accordingly. Monitoring
|
||||
changes in tissue oxygen saturation and utilization helps
|
||||
identify an individual's optimal intensity to work at, as well
|
||||
as how well they recover between bouts of intensity. This can
|
||||
help optimize training to improve performance, prevent
|
||||
overtraining, and tailor strategies for better results.
|
||||
</p>
|
||||
<p>
|
||||
During competitions, athletes can also use SmO2 data to pace
|
||||
themselves effectively. Adjusting intensity based on muscle
|
||||
oxygenation can help prevent premature fatigue and optimize race
|
||||
outcomes
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- VO2 Master Chart Section -->
|
||||
<div class="mb-4">
|
||||
<h2 class="text-xl font-bold mb-4 text-center">
|
||||
VO2 Master Chart
|
||||
</h2>
|
||||
|
||||
<!-- Male VO2 Table -->
|
||||
<div class="mb-4">
|
||||
<table
|
||||
class="w-full border-collapse border border-gray-300 text-xs"
|
||||
>
|
||||
<thead>
|
||||
<tr class="bg-cyan-200">
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Age (M)
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Very Poor
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Poor
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Fair
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Good
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Excellent
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Superior
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
20-29
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
29.0-38.1
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
38.1-44.9
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
44.9-50.2
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
50.2-61.8
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
57.1-66.3
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
66.3+
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
30-39
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
27.2-34.1
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
34.1-39.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
39.6-45.2
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
45.2-51.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
51.6-59.8
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
59.8+
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
40-49
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
24.2-30.5
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
30.5-35.7
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
35.7-40.3
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
40.3-46.7
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
46.7-55.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
55.6+
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
50-59
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
20.9-26.1
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
26.1-30.7
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
30.7-35.1
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
35.1-41.2
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
41.2-50.7
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
50.7+
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
60-69
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
17.4-22.4
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
22.4-26.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
26.6-30.5
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
30.5-36.1
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
36.1-43.0
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
43.0+
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Female VO2 Table -->
|
||||
<div class="mb-4">
|
||||
<table
|
||||
class="w-full border-collapse border border-gray-300 text-xs"
|
||||
>
|
||||
<thead>
|
||||
<tr class="bg-cyan-200">
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Age (F)
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Very Poor
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Poor
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Fair
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Good
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Excellent
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Superior
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
20-29
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
21.7-28.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
28.6-34.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
34.6-40.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
40.6-46.5
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
46.5-56.0
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
56.0+
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
30-39
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
19.0-24.1
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
24.1-28.2
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
28.2-32.2
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
32.2-35.7
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
35.7-45.8
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
45.8+
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
40-49
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
17.0-21.3
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
21.3-24.9
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
24.9-28.7
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
28.7-34.0
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
34.0-41.7
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
41.7+
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
50-59
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
16.0-19.1
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
19.1-24.4
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
21.8-27.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
25.2-28.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
28.6-35.9
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
35.9+
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
60-69
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
13.4-16.5
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
16.5-18.9
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
18.9-21.2
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
21.2-24.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
24.6-29.4
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
29.4+
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="w-full max-w-5xl">
|
||||
<h1 class="text-2xl font-bold mb-4 text-center">
|
||||
Body Fat Percent Master Chart
|
||||
</h1>
|
||||
<img
|
||||
src="data:image/png;base64,{{ body_fat_percentage_chart }}"
|
||||
alt="Body Fat Percentage"
|
||||
class="w-full h-auto object-contain chart-large"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,814 @@
|
||||
<div class="w-full page bg-white">
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="p-6">
|
||||
<h1 class="text-3xl font-bold mb-6">Glossary</h1>
|
||||
|
||||
<!-- Resting Heart Rate Section -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-bold mb-4 text-center">
|
||||
Resting Heart Rate
|
||||
</h2>
|
||||
|
||||
<!-- Male Table -->
|
||||
<div class="mb-4">
|
||||
<table
|
||||
class="w-full border-collapse border border-gray-300 text-xs"
|
||||
>
|
||||
<thead>
|
||||
<tr class="bg-cyan-200">
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Age (M)
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Poor
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Below Average
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Average
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Above Average
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Good
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Excellent
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Athlete
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
18-25
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
85bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
76-84bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
74-78bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
70-73bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
66-69bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
61-65bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
60-60bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
26-35
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
83bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
77-82bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
73-76bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
69-72bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
65-68bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
60-64bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
55-59bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
36-45
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
85bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
79-84bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
74-78bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
70-73bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
65-69bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
60-64bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
55-59bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
46-55
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
84bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
76-83bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
73-77bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
70-72bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
66-69bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
61-65bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
56-60bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
56-65
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
85bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
78-84bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
74-77bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
70-73bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
65-69bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
60-64bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
50-59bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
65+
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
84bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
77-83bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
73-76bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
70-73bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
65-69bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
60-64bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
55-59bpm
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Female Table -->
|
||||
<div class="mb-4">
|
||||
<table
|
||||
class="w-full border-collapse border border-gray-300 text-xs"
|
||||
>
|
||||
<thead>
|
||||
<tr class="bg-cyan-200">
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Age (F)
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Poor
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Below Average
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Average
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Above Average
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Good
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Excellent
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Athlete
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
18-25
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
81bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
74-81bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
73-78bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
66-69bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
62-65bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
56-61bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
50-55bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
26-35
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
82bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
75-81bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
71-74bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
66-70bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
62-65bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
55-61bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
54-54bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
36-45
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
83bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
76-82bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
71-75bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
67-70bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
63-66bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
57-62bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
47-56bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
46-55
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
84bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
77-83bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
72-76bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
68-71bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
64-67bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
58-63bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
49-57bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
56-65
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
82bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
76-81bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
72-75bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
68-71bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
62-67bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
57-61bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
51-56bpm
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
65+
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
80bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
74-79bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
70-73bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
66-69bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
62-65bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
56-61bpm
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
52-55bpm
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VO2 Master Chart Section -->
|
||||
<div class="mb-4">
|
||||
<h2 class="text-xl font-bold mb-4 text-center">
|
||||
VO2 Master Chart
|
||||
</h2>
|
||||
|
||||
<!-- Male VO2 Table -->
|
||||
<div class="mb-4">
|
||||
<table
|
||||
class="w-full border-collapse border border-gray-300 text-xs"
|
||||
>
|
||||
<thead>
|
||||
<tr class="bg-cyan-200">
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Age (M)
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Very Poor
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Poor
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Fair
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Good
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Excellent
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Superior
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
20-29
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
29.0-38.1
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
38.1-44.9
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
44.9-50.2
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
50.2-61.8
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
57.1-66.3
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
66.3+
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
30-39
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
27.2-34.1
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
34.1-39.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
39.6-45.2
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
45.2-51.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
51.6-59.8
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
59.8+
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
40-49
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
24.2-30.5
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
30.5-35.7
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
35.7-40.3
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
40.3-46.7
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
46.7-55.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
55.6+
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
50-59
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
20.9-26.1
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
26.1-30.7
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
30.7-35.1
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
35.1-41.2
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
41.2-50.7
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
50.7+
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
60-69
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
17.4-22.4
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
22.4-26.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
26.6-30.5
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
30.5-36.1
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
36.1-43.0
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
43.0+
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Female VO2 Table -->
|
||||
<div class="mb-4">
|
||||
<table
|
||||
class="w-full border-collapse border border-gray-300 text-xs"
|
||||
>
|
||||
<thead>
|
||||
<tr class="bg-cyan-200">
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Age (F)
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Very Poor
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Poor
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Fair
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Good
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Excellent
|
||||
</th>
|
||||
<th
|
||||
class="border border-gray-300 p-1 font-bold text-center"
|
||||
>
|
||||
Superior
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
20-29
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
21.7-28.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
28.6-34.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
34.6-40.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
40.6-46.5
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
46.5-56.0
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
56.0+
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
30-39
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
19.0-24.1
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
24.1-28.2
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
28.2-32.2
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
32.2-35.7
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
35.7-45.8
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
45.8+
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
40-49
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
17.0-21.3
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
21.3-24.9
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
24.9-28.7
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
28.7-34.0
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
34.0-41.7
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
41.7+
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
50-59
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
16.0-19.1
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
19.1-24.4
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
21.8-27.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
25.2-28.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
28.6-35.9
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
35.9+
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="border border-gray-300 p-1 font-medium text-center"
|
||||
>
|
||||
60-69
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
13.4-16.5
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
16.5-18.9
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
18.9-21.2
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
21.2-24.6
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
24.6-29.4
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
29.4+
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
+37
-193
@@ -1,198 +1,42 @@
|
||||
<div class="w-full page bg-white">
|
||||
<!-- Main Content -->
|
||||
<div class="px-8 py-6">
|
||||
<!-- Page Title -->
|
||||
<h1 class="text-3xl font-bold text-black mb-6">Cardio Metrics</h1>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="px-8 py-6">
|
||||
<!-- Page Title -->
|
||||
<h1 class="text-3xl font-bold text-black mb-6">Cardio Metrics</h1>
|
||||
|
||||
<!-- Active Metabolic Rate Assessment Section -->
|
||||
<h2 class="text-xl font-bold text-black mb-4">Active Metabolic Rate Assessment</h2>
|
||||
<p class="text-gray-700 text-sm mb-8">The active metabolic rate assessment is a key measure of aerobic fitness. It helps determine your specific heart rate zones and how well your body uses carbohydrates and fats as fuel while you exercise. It is also an indicator of overall health and wellbeing.</p>
|
||||
<!-- Active Metabolic Rate Assessment Section -->
|
||||
<h2 class="text-xl font-bold text-black mb-4">
|
||||
Active Metabolic Rate Assessment
|
||||
</h2>
|
||||
<p class="text-gray-700 text-sm mb-8">
|
||||
The active metabolic rate assessment is a key measure of aerobic
|
||||
fitness. It helps determine your specific heart rate zones and how
|
||||
well your body uses carbohydrates and fats as fuel while you
|
||||
exercise. It is also an indicator of overall health and wellbeing.
|
||||
</p>
|
||||
|
||||
<!-- VO2 Max Section -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-xl font-bold text-black mb-4 text-center">VO2 Max - {{ vo2_max_value | default('49.5') }} ({{ vo2_max_percentile | default('100th percentile') }})</h3>
|
||||
|
||||
<!-- VO2 Max Table -->
|
||||
<div class="mb-8">
|
||||
<table class="w-full border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-3 text-black font-bold">Age (F)</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-3 text-black font-bold">Very Poor</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-3 text-black font-bold">Poor</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-3 text-black font-bold">Fair</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-3 text-black font-bold">Good</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-3 text-black font-bold">Excellent</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-3 text-black font-bold relative">
|
||||
Superior
|
||||
<!-- Arrow indicator -->
|
||||
<div class="absolute -bottom-4 left-1/2 transform -translate-x-1/2">
|
||||
<div class="w-0 h-0 border-l-4 border-r-4 border-t-8 border-transparent border-t-black"></div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="bg-cyan-200 border border-gray-400 p-3 text-black font-semibold">{{ age_range | default('30-39') }}</td>
|
||||
<td class="bg-gray-100 border border-gray-400 p-3 text-black text-center">{{ very_poor_range | default('19.0-24.1') }}</td>
|
||||
<td class="bg-gray-100 border border-gray-400 p-3 text-black text-center">{{ poor_range | default('24.1-28.2') }}</td>
|
||||
<td class="bg-gray-100 border border-gray-400 p-3 text-black text-center">{{ fair_range | default('28.2-32.2') }}</td>
|
||||
<td class="bg-gray-100 border border-gray-400 p-3 text-black text-center">{{ good_range | default('32.2-35.7') }}</td>
|
||||
<td class="bg-gray-100 border border-gray-400 p-3 text-black text-center">{{ excellent_range | default('35.7-45.8') }}</td>
|
||||
<td class="bg-gray-100 border border-gray-400 p-3 text-black text-center font-bold">{{ superior_range | default('45.8+') }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- VO2 Max Section -->
|
||||
<div class="mb-8">
|
||||
<!-- VO2 Max Table -->
|
||||
<div class="mb-8 flex justify-center">
|
||||
<img
|
||||
src="data:image/png;base64, {{ vo2_max_table }}"
|
||||
alt="VO2 Max Table"
|
||||
class="table-image"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Personalized Heart Rate Zones Section -->
|
||||
<div class="mb-8">
|
||||
<!-- Heart Rate Zones Table -->
|
||||
<div class="flex justify-center">
|
||||
<img
|
||||
src="data:image/png;base64, {{ hr_zones_table }}"
|
||||
alt="Heart Rate Zones Table"
|
||||
class="table-image"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Personalized Heart Rate Zones Section -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-xl font-bold text-black mb-6 text-center">Personalized Heart Rate Zones</h3>
|
||||
|
||||
<!-- Heart Rate Zones Table -->
|
||||
<table class="w-full border-collapse text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold">Zone 1</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold">Zone 2</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold">Zone 3</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold">Zone 4</th>
|
||||
<th class="bg-cyan-300 border border-gray-400 p-2 text-black font-bold">Zone 5</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Zone Descriptions -->
|
||||
<tr>
|
||||
<td class="border border-gray-400 p-3 text-center">
|
||||
<div class="text-black font-semibold mb-1">Improves health and recovery capacity</div>
|
||||
</td>
|
||||
<td class="border border-gray-400 p-3 text-center">
|
||||
<div class="text-black font-semibold mb-1">Improves endurance and fat burning</div>
|
||||
</td>
|
||||
<td class="border border-gray-400 p-3 text-center">
|
||||
<div class="text-black font-semibold mb-1">Improves Aerobic fitness</div>
|
||||
</td>
|
||||
<td class="border border-gray-400 p-3 text-center">
|
||||
<div class="text-black font-semibold mb-1">Improves maximum performance capacity</div>
|
||||
</td>
|
||||
<td class="border border-gray-400 p-3 text-center">
|
||||
<div class="text-black font-semibold mb-1">Develops maximum performance and speed</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Heart Rate Percentages -->
|
||||
<tr>
|
||||
<td class="border border-gray-400 p-2 text-center text-black font-semibold">{{ zone1_percentage | default('55-65% of Max Heart Rate') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black font-semibold">{{ zone2_percentage | default('65-75% of Max Heart Rate') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black font-semibold">{{ zone3_percentage | default('80-85% of Max Heart Rate') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black font-semibold">{{ zone4_percentage | default('85-88% of Max Heart Rate') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black font-semibold">{{ zone5_percentage | default('90% of Max Heart Rate') }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Heart Rate BPM -->
|
||||
<tr>
|
||||
<td class="bg-red-200 border border-gray-400 p-2 text-center text-black font-bold">{{ zone1_bpm | default('81-96bpm') }}</td>
|
||||
<td class="bg-red-200 border border-gray-400 p-2 text-center text-black font-bold">{{ zone2_bpm | default('96-100bpm') }}</td>
|
||||
<td class="bg-yellow-200 border border-gray-400 p-2 text-center text-black font-bold">{{ zone3_bpm | default('100-178bpm') }}</td>
|
||||
<td class="bg-green-200 border border-gray-400 p-2 text-center text-black font-bold">{{ zone4_bpm | default('178-188bpm') }}</td>
|
||||
<td class="bg-green-200 border border-gray-400 p-2 text-center text-black font-bold">{{ zone5_bpm | default('188-198bpm') }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Speed -->
|
||||
<tr>
|
||||
<td class="border border-gray-400 p-2 text-center">
|
||||
<div class="text-black font-semibold">{{ zone1_speed | default('3.5mph') }}</div>
|
||||
<div class="text-black text-xs">{{ zone1_incline | default('2% Incline') }}</div>
|
||||
</td>
|
||||
<td class="border border-gray-400 p-2 text-center">
|
||||
<div class="text-black font-semibold">{{ zone2_speed | default('3.5-4.0mph') }}</div>
|
||||
<div class="text-black text-xs">{{ zone2_incline | default('2% Incline') }}</div>
|
||||
</td>
|
||||
<td class="border border-gray-400 p-2 text-center">
|
||||
<div class="text-black font-semibold">{{ zone3_speed | default('4.0-6.5mph') }}</div>
|
||||
<div class="text-black text-xs">{{ zone3_incline | default('2% Incline') }}</div>
|
||||
</td>
|
||||
<td class="border border-gray-400 p-2 text-center">
|
||||
<div class="text-black font-semibold">{{ zone4_speed | default('6.5-7.0mph') }}</div>
|
||||
<div class="text-black text-xs">{{ zone4_incline | default('2% Incline') }}</div>
|
||||
</td>
|
||||
<td class="border border-gray-400 p-2 text-center">
|
||||
<div class="text-black font-semibold">{{ zone5_speed | default('7.0-8.0mph') }}</div>
|
||||
<div class="text-black text-xs">{{ zone5_incline | default('2% Incline') }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Pace -->
|
||||
<tr>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ zone1_pace | default('10:39min/km Pace') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ zone2_pace | default('10:39-9:19min/km Pace') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ zone3_pace | default('9:19-5:44min/km Pace') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ zone4_pace | default('5:44-5:20min/km Pace') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ zone5_pace | default('5:20-4:40min/km Pace') }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Average Calories -->
|
||||
<tr>
|
||||
<td class="border border-gray-400 p-2 text-center">
|
||||
<div class="text-black text-xs">Avg:</div>
|
||||
<div class="text-black font-semibold">{{ zone1_calories | default('4.4kcals/minute') }}</div>
|
||||
</td>
|
||||
<td class="border border-gray-400 p-2 text-center">
|
||||
<div class="text-black text-xs">Avg:</div>
|
||||
<div class="text-black font-semibold">{{ zone2_calories | default('5.9kcals/minute') }}</div>
|
||||
</td>
|
||||
<td class="border border-gray-400 p-2 text-center">
|
||||
<div class="text-black text-xs">Avg:</div>
|
||||
<div class="text-black font-semibold">{{ zone3_calories | default('9.4kcals/minute') }}</div>
|
||||
</td>
|
||||
<td class="border border-gray-400 p-2 text-center">
|
||||
<div class="text-black text-xs">Avg:</div>
|
||||
<div class="text-black font-semibold">{{ zone4_calories | default('12.5kcals/minute') }}</div>
|
||||
</td>
|
||||
<td class="border border-gray-400 p-2 text-center">
|
||||
<div class="text-black text-xs">Avg:</div>
|
||||
<div class="text-black font-semibold">{{ zone5_calories | default('12.8kcals/minute') }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Carb Utilization -->
|
||||
<tr>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ zone1_carb | default('Avg: 0.4g/min Carb Utilization') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ zone2_carb | default('Avg: 0.6g/min Carb Utilization') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ zone3_carb | default('Avg: 1.9g/min Carb Utilization') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ zone4_carb | default('Avg: 2.9g/min Carb Utilization') }}</td>
|
||||
<td class="border border-gray-400 p-2 text-center text-black">{{ zone5_carb | default('Avg: 3.1g/min Carb Utilization') }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Breathing -->
|
||||
<tr>
|
||||
<td class="bg-red-200 border border-gray-400 p-2 text-center">
|
||||
<div class="text-black font-bold">{{ zone1_breaths | default('Avg: 27 breaths') }}</div>
|
||||
<div class="text-black text-xs italic">{{ zone1_breath_range | default('Ideal Range: 15-20 breaths') }}</div>
|
||||
</td>
|
||||
<td class="bg-red-200 border border-gray-400 p-2 text-center">
|
||||
<div class="text-black font-bold">{{ zone2_breaths | default('Avg: 28 breaths') }}</div>
|
||||
<div class="text-black text-xs italic">{{ zone2_breath_range | default('Ideal Range: 20-25 breaths') }}</div>
|
||||
</td>
|
||||
<td class="bg-yellow-200 border border-gray-400 p-2 text-center">
|
||||
<div class="text-black font-bold">{{ zone3_breaths | default('Avg: 31 breaths') }}</div>
|
||||
<div class="text-black text-xs italic">{{ zone3_breath_range | default('Ideal Range: 25-30 breaths') }}</div>
|
||||
</td>
|
||||
<td class="bg-green-200 border border-gray-400 p-2 text-center">
|
||||
<div class="text-black font-bold">{{ zone4_breaths | default('Avg: 42 breaths') }}</div>
|
||||
<div class="text-black text-xs italic">{{ zone4_breath_range | default('Ideal Range: 30-35 breaths') }}</div>
|
||||
</td>
|
||||
<td class="bg-green-200 border border-gray-400 p-2 text-center">
|
||||
<div class="text-black font-bold">{{ zone5_breaths | default('Avg: 51 breaths') }}</div>
|
||||
<div class="text-black text-xs italic">{{ zone5_breath_range | default('Ideal Range: 40+ breaths') }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6,7 +6,7 @@ of the medical report. It performs analysis on Pnoe, Spirometry, and SECA data.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Dict, Optional, Tuple
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
|
||||
import pandas as pd
|
||||
|
||||
@@ -18,6 +18,7 @@ class ContextGenerator:
|
||||
self.pnoe_df = None
|
||||
self.spirometry_df = None
|
||||
self.seca_df = None
|
||||
self.oxygenation_df = None
|
||||
self.patient_info = {}
|
||||
|
||||
def load_data(
|
||||
@@ -25,6 +26,7 @@ class ContextGenerator:
|
||||
pnoe_path: str,
|
||||
spirometry_path: str,
|
||||
seca_path: Optional[str] = None,
|
||||
oxygenation_path: Optional[str] = None,
|
||||
):
|
||||
"""Load all required datasets"""
|
||||
self.pnoe_df = pd.read_csv(pnoe_path, delimiter=";")
|
||||
@@ -33,6 +35,11 @@ class ContextGenerator:
|
||||
self.seca_df = pd.read_excel(seca_path)
|
||||
else:
|
||||
self.seca_df = None
|
||||
if oxygenation_path:
|
||||
# Load muscle oxygenation data with skiprows to skip Train.Red metadata
|
||||
self.oxygenation_df = pd.read_csv(oxygenation_path, skiprows=445)
|
||||
else:
|
||||
self.oxygenation_df = None
|
||||
self._preprocess_pnoe_data()
|
||||
|
||||
def _preprocess_pnoe_data(self):
|
||||
@@ -235,19 +242,27 @@ class ContextGenerator:
|
||||
return metrics
|
||||
|
||||
def _detect_thresholds(self) -> Tuple[Optional[Dict], Optional[Dict]]:
|
||||
"""Detect VT1 and VT2 thresholds"""
|
||||
"""Detect VT1 and VT2 thresholds (matching notebook logic)"""
|
||||
# VT1: First index where carb burn > fat burn AND remains higher
|
||||
condition = self.pnoe_df["CHO_smoothed"] > self.pnoe_df["FAT_smoothed"]
|
||||
crossover_indices = condition[condition].index
|
||||
|
||||
vt1 = None
|
||||
if len(crossover_indices) > 0:
|
||||
vt1_idx = crossover_indices[0]
|
||||
vt1_row = self.pnoe_df.loc[vt1_idx]
|
||||
vt1 = {
|
||||
"HeartRate": vt1_row["HR(bpm)_smoothed"],
|
||||
"Speed": vt1_row["Speed"],
|
||||
"Time": vt1_row["T(sec)"],
|
||||
}
|
||||
# Find first crossover where carbs remain higher for the rest
|
||||
for idx in crossover_indices:
|
||||
if all(
|
||||
self.pnoe_df.loc[idx:]["CHO_smoothed"]
|
||||
> self.pnoe_df.loc[idx:]["FAT_smoothed"]
|
||||
):
|
||||
vt1_idx = idx
|
||||
vt1_row = self.pnoe_df.loc[vt1_idx]
|
||||
vt1 = {
|
||||
"HeartRate": vt1_row["HR(bpm)_smoothed"],
|
||||
"Speed": vt1_row["Speed"],
|
||||
"Time": vt1_row["T(sec)"],
|
||||
}
|
||||
break
|
||||
|
||||
ve_slope = self.pnoe_df["VE(l/min)_smoothed"].diff()
|
||||
second_derivative = ve_slope.diff()
|
||||
@@ -552,6 +567,476 @@ class ContextGenerator:
|
||||
"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": age_range,
|
||||
"ranges": formatted_ranges,
|
||||
"raw_ranges": ranges, # Keep raw ranges for category determination
|
||||
}
|
||||
|
||||
def _determine_rhr_category(self, rhr: float, age: int, gender: str) -> str:
|
||||
"""Determine resting heart rate category based on value, age, and gender (matching notebook logic)"""
|
||||
rhr_table_info = self._calculate_rhr_table_data(age, gender)
|
||||
ranges = rhr_table_info["raw_ranges"]
|
||||
|
||||
# Check Poor category first (open-ended at top)
|
||||
min_val, max_val = ranges["Poor"]
|
||||
if max_val is None and rhr >= min_val:
|
||||
return "Poor"
|
||||
|
||||
# Check other categories from Below Average down to Athlete
|
||||
# For RHR, lower is better, so we check from highest to lowest
|
||||
for category in [
|
||||
"Below Average",
|
||||
"Average",
|
||||
"Above Average",
|
||||
"Good",
|
||||
"Excellent",
|
||||
"Athlete",
|
||||
]:
|
||||
min_val, max_val = ranges[category]
|
||||
# Check if value falls in this range (inclusive of min, exclusive of max)
|
||||
if min_val <= rhr < max_val:
|
||||
return category
|
||||
|
||||
# If value is below all ranges (below Athlete minimum), return Athlete
|
||||
# This handles the case where rhr < min of Athlete
|
||||
return "Athlete"
|
||||
|
||||
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 - use optimal fat burning zone (highest fat:carb ratio)
|
||||
# matching notebook logic
|
||||
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]
|
||||
|
||||
# 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]
|
||||
|
||||
# HR BPM Range - match notebook exactly
|
||||
hr_bpm_str = f"{int(start)}-{int(end)} bpm"
|
||||
|
||||
if not zone_df.empty:
|
||||
# Speed calculation - match notebook exactly
|
||||
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}\nmin/km Pace"
|
||||
else:
|
||||
speed_str = "-\n2% Incline"
|
||||
pace_str = "-"
|
||||
|
||||
# Calories (using raw EE) - match notebook exactly
|
||||
avg_cals = zone_df["EE(kcal/min)"].mean()
|
||||
calories_str = f"Avg:\n{avg_cals:.1f} kcals/minute"
|
||||
|
||||
# Carb utilization (g/min) - match notebook exactly
|
||||
avg_carbs_g = zone_df["CHO"].mean() / 4
|
||||
carb_str = f"Avg: {avg_carbs_g:.1f}g/min\nCarb Utilization"
|
||||
|
||||
# Breathing - match notebook exactly
|
||||
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": hr_bpm_str,
|
||||
"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 - matching exact values)
|
||||
vo2_max_data = {
|
||||
"20-29 (M)": {
|
||||
"Very Poor": (29.0, 38.1),
|
||||
"Poor": (38.1, 44.9),
|
||||
"Fair": (44.9, 50.2),
|
||||
"Good": (50.2, 61.8),
|
||||
"Excellent": (57.1, 66.3),
|
||||
"Superior": (66.3, None),
|
||||
},
|
||||
"30-39 (M)": {
|
||||
"Very Poor": (27.2, 34.1),
|
||||
"Poor": (34.1, 39.6),
|
||||
"Fair": (39.6, 45.2),
|
||||
"Good": (45.2, 51.6),
|
||||
"Excellent": (51.6, 59.8),
|
||||
"Superior": (59.8, None),
|
||||
},
|
||||
"40-49 (M)": {
|
||||
"Very Poor": (24.2, 30.5),
|
||||
"Poor": (30.5, 35.7),
|
||||
"Fair": (35.7, 40.3),
|
||||
"Good": (40.3, 46.7),
|
||||
"Excellent": (46.7, 55.6),
|
||||
"Superior": (55.6, None),
|
||||
},
|
||||
"50-59 (M)": {
|
||||
"Very Poor": (20.9, 26.1),
|
||||
"Poor": (26.1, 30.7),
|
||||
"Fair": (30.7, 35.1),
|
||||
"Good": (35.1, 41.2),
|
||||
"Excellent": (41.2, 50.7),
|
||||
"Superior": (50.7, None),
|
||||
},
|
||||
"60-69 (M)": {
|
||||
"Very Poor": (17.4, 22.4),
|
||||
"Poor": (22.4, 26.6),
|
||||
"Fair": (26.6, 30.5),
|
||||
"Good": (30.5, 36.1),
|
||||
"Excellent": (36.1, 43.0),
|
||||
"Superior": (43.0, None),
|
||||
},
|
||||
"20-29 (F)": {
|
||||
"Very Poor": (21.7, 28.6),
|
||||
"Poor": (28.6, 34.6),
|
||||
"Fair": (34.6, 40.6),
|
||||
"Good": (40.6, 46.5),
|
||||
"Excellent": (46.5, 56.0),
|
||||
"Superior": (56.0, None),
|
||||
},
|
||||
"30-39 (F)": {
|
||||
"Very Poor": (19.0, 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": (17.0, 21.3),
|
||||
"Poor": (21.3, 24.9),
|
||||
"Fair": (24.9, 28.7),
|
||||
"Good": (28.7, 34.0),
|
||||
"Excellent": (34.0, 41.7),
|
||||
"Superior": (41.7, None),
|
||||
},
|
||||
"50-59 (F)": {
|
||||
"Very Poor": (16.0, 19.1),
|
||||
"Poor": (19.1, 24.4),
|
||||
"Fair": (21.8, 27.6),
|
||||
"Good": (25.2, 28.6),
|
||||
"Excellent": (28.6, 35.9),
|
||||
"Superior": (35.9, None),
|
||||
},
|
||||
"60-69 (F)": {
|
||||
"Very Poor": (13.4, 16.5),
|
||||
"Poor": (16.5, 18.9),
|
||||
"Fair": (18.9, 21.2),
|
||||
"Good": (21.2, 24.6),
|
||||
"Excellent": (24.6, 29.4),
|
||||
"Superior": (29.4, None),
|
||||
},
|
||||
}
|
||||
|
||||
# Determine age bracket (matching notebook logic)
|
||||
if 20 <= age <= 29:
|
||||
age_key = "20-29"
|
||||
elif 30 <= age <= 39:
|
||||
age_key = "30-39"
|
||||
elif 40 <= age <= 49:
|
||||
age_key = "40-49"
|
||||
elif 50 <= age <= 59:
|
||||
age_key = "50-59"
|
||||
elif 60 <= age <= 69:
|
||||
age_key = "60-69"
|
||||
else:
|
||||
# Default to closest range
|
||||
if age < 20:
|
||||
age_key = "20-29"
|
||||
elif age >= 70:
|
||||
age_key = "60-69"
|
||||
else:
|
||||
age_key = "30-39" # fallback
|
||||
|
||||
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,
|
||||
"raw_ranges": ranges, # Keep raw ranges for category determination
|
||||
}
|
||||
|
||||
def _determine_vo2_max_category(self, vo2_max: float, age: int, gender: str) -> str:
|
||||
"""Determine VO2 max category based on value, age, and gender (matching notebook logic)"""
|
||||
vo2_max_table_info = self._calculate_vo2_max_table_data(age, gender)
|
||||
ranges = vo2_max_table_info["raw_ranges"]
|
||||
|
||||
categories = ["Very Poor", "Poor", "Fair", "Good", "Excellent", "Superior"]
|
||||
|
||||
# Check Superior category first (open-ended)
|
||||
min_val, max_val = ranges["Superior"]
|
||||
if max_val is None and vo2_max >= min_val:
|
||||
return "Superior"
|
||||
|
||||
# Check other categories from Excellent down to Very Poor
|
||||
# Ranges are typically [min, max) - inclusive of min, exclusive of max
|
||||
for category in reversed(
|
||||
categories[:-1]
|
||||
): # Exclude Superior as we already checked it
|
||||
min_val, max_val = ranges[category]
|
||||
# Check if value falls in this range (inclusive of min, exclusive of max)
|
||||
if min_val <= vo2_max < max_val:
|
||||
return category
|
||||
|
||||
# If value is below all ranges, return Very Poor
|
||||
# This handles the case where vo2_max < min of Very Poor
|
||||
return "Very Poor"
|
||||
|
||||
def calculate_rmr_and_fuel_source(self) -> Dict:
|
||||
"""Calculate RMR and fuel source from pnoe data"""
|
||||
metrics = {}
|
||||
@@ -626,6 +1111,7 @@ class ContextGenerator:
|
||||
patient_name: str,
|
||||
graphs: Dict[str, str],
|
||||
metric_overrides: Optional[Dict] = None,
|
||||
graph_generator: Optional[Any] = None,
|
||||
) -> Dict[str, Dict]:
|
||||
"""Main method to generate all page contexts
|
||||
|
||||
@@ -720,6 +1206,95 @@ class ContextGenerator:
|
||||
"vo2_pulse_chart": graphs.get("vo2_pulse", ""),
|
||||
}
|
||||
|
||||
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"]
|
||||
)
|
||||
|
||||
# Determine patient's VO2 max category
|
||||
vo2_max_value = pnoe_metrics.get("vo2_max_per_kg", 0.0)
|
||||
category = self._determine_vo2_max_category(
|
||||
vo2_max_value,
|
||||
self.patient_info["age"],
|
||||
self.patient_info["gender"],
|
||||
)
|
||||
|
||||
# VO2 Max Table
|
||||
gender_label = (
|
||||
"F" if self.patient_info["gender"].lower() == "female" else "M"
|
||||
)
|
||||
age_range_label = f"{vo2_max_table_info['age_range']} ({gender_label})"
|
||||
|
||||
vo2_max_columns = [
|
||||
"Age",
|
||||
"Very Poor",
|
||||
"Poor",
|
||||
"Fair",
|
||||
"Good",
|
||||
"Excellent",
|
||||
"Superior",
|
||||
]
|
||||
vo2_max_data = [
|
||||
[
|
||||
age_range_label,
|
||||
vo2_max_table_info["ranges"]["Very Poor"],
|
||||
vo2_max_table_info["ranges"]["Poor"],
|
||||
vo2_max_table_info["ranges"]["Fair"],
|
||||
vo2_max_table_info["ranges"]["Good"],
|
||||
vo2_max_table_info["ranges"]["Excellent"],
|
||||
vo2_max_table_info["ranges"]["Superior"],
|
||||
]
|
||||
]
|
||||
|
||||
contexts["page_8"]["vo2_max_table"] = (
|
||||
graph_generator.generate_vo2_max_table(
|
||||
data=vo2_max_data,
|
||||
columns=vo2_max_columns,
|
||||
vo2_max_value=vo2_max_value,
|
||||
category=category,
|
||||
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
|
||||
hr_zones_columns = ["Zone 1", "Zone 2", "Zone 3", "Zone 4", "Zone 5"]
|
||||
hr_zones_data = [
|
||||
[
|
||||
"Improves health and\nrecovery capacity",
|
||||
"Improves endurance\nand fat burning",
|
||||
"Improves Aerobic\nfitness",
|
||||
"Improves maximum\nperformance capacity",
|
||||
"Develops maximum\nperformance and speed",
|
||||
],
|
||||
[
|
||||
"55-65% of Max Heart Rate",
|
||||
"65-75% of Max Heart Rate",
|
||||
"80-85% of Max Heart Rate",
|
||||
"85-88% of Max Heart Rate",
|
||||
"90%+ of Max Heart Rate",
|
||||
],
|
||||
[zone_metrics["zones"][i]["hr_bpm"] for i in range(5)],
|
||||
[zone_metrics["zones"][i]["speed"] for i in range(5)],
|
||||
[zone_metrics["zones"][i]["pace"] for i in range(5)],
|
||||
[zone_metrics["zones"][i]["calories"] for i in range(5)],
|
||||
[zone_metrics["zones"][i]["carb"] for i in range(5)],
|
||||
[zone_metrics["zones"][i]["breathing"] for i in range(5)],
|
||||
]
|
||||
# Colors are now handled directly in the graph generator to match notebook
|
||||
# No need to pass cell_colors
|
||||
contexts["page_8"]["hr_zones_table"] = (
|
||||
graph_generator.generate_heart_rate_zones_table(
|
||||
data=hr_zones_data,
|
||||
columns=hr_zones_columns,
|
||||
save_as_base64=True,
|
||||
)
|
||||
)
|
||||
|
||||
# Page 9
|
||||
contexts["page_9"] = {
|
||||
"fat_max_value": f"{pnoe_metrics['fat_max_value']:.2f}",
|
||||
@@ -728,9 +1303,18 @@ class ContextGenerator:
|
||||
"fat_metabolism_chart": graphs.get("fat_metabolism", ""),
|
||||
}
|
||||
|
||||
# Page 10 - VO2 Pulse and VO2 Breath
|
||||
vo2_drop_metrics = self._calculate_vo2_drop_points(pnoe_metrics)
|
||||
# Page 10 - Fuelling Analysis (new page with flowchart and carb storage table)
|
||||
contexts["page_10"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": 10,
|
||||
"fuelling_analysis_flowchart": graphs.get(
|
||||
"fuelling_analysis_flowchart", ""
|
||||
),
|
||||
}
|
||||
|
||||
# Page 11 - VO2 Pulse and VO2 Breath (previously page 10)
|
||||
vo2_drop_metrics = self._calculate_vo2_drop_points(pnoe_metrics)
|
||||
contexts["page_11"] = {
|
||||
"vo2_pulse_chart": graphs.get("vo2_pulse", ""),
|
||||
"vo2_breath_chart": graphs.get("vo2_breath", ""),
|
||||
"vo2_pulse_drop_bpm": f"{vo2_drop_metrics['vo2_pulse_drop_bpm']} bpm",
|
||||
@@ -739,12 +1323,12 @@ class ContextGenerator:
|
||||
"vo2_breath_drop_zone": vo2_drop_metrics["vo2_breath_drop_zone"],
|
||||
}
|
||||
|
||||
# Page 11 - Fat Metabolism and Recovery
|
||||
# Page 12 - Fat Metabolism and Recovery (previously page 11)
|
||||
fat_metabolism_metrics = self._calculate_fat_metabolism_metrics(pnoe_metrics)
|
||||
recovery_metrics = self._calculate_recovery_metrics()
|
||||
resting_hr_metrics = self._calculate_resting_heart_rate_metrics()
|
||||
|
||||
contexts["page_11"] = {
|
||||
contexts["page_12"] = {
|
||||
"fat_metabolism_chart": graphs.get("fat_metabolism", ""),
|
||||
"recovery_chart": graphs.get("recovery", ""),
|
||||
**fat_metabolism_metrics,
|
||||
@@ -752,26 +1336,98 @@ class ContextGenerator:
|
||||
**resting_hr_metrics,
|
||||
}
|
||||
|
||||
# Pages 12-17
|
||||
for i in range(6):
|
||||
contexts[f"page_{i + 12}"] = {
|
||||
if graph_generator:
|
||||
# Page 12 Resting Heart Rate Table (previously page 11)
|
||||
rhr_table_info = self._calculate_rhr_table_data(
|
||||
self.patient_info["age"], self.patient_info["gender"]
|
||||
)
|
||||
|
||||
# Get resting heart rate value and determine category
|
||||
# Extract numeric value from "53bpm" format (resting_hr_metrics already calculated above)
|
||||
rhr_value_str = resting_hr_metrics.get("resting_heart_rate", "0bpm")
|
||||
rhr_value = float(rhr_value_str.replace("bpm", "").strip())
|
||||
|
||||
category = self._determine_rhr_category(
|
||||
rhr_value,
|
||||
self.patient_info["age"],
|
||||
self.patient_info["gender"],
|
||||
)
|
||||
|
||||
gender_label = (
|
||||
"F" if self.patient_info["gender"].lower().startswith("f") else "M"
|
||||
)
|
||||
age_range_label = f"{rhr_table_info['age_range']} ({gender_label})"
|
||||
|
||||
rhr_columns = [
|
||||
"Age",
|
||||
"Poor",
|
||||
"Below Average",
|
||||
"Average",
|
||||
"Above Average",
|
||||
"Good",
|
||||
"Excellent",
|
||||
"Athlete",
|
||||
]
|
||||
rhr_data = [
|
||||
[
|
||||
age_range_label,
|
||||
rhr_table_info["ranges"]["Poor"],
|
||||
rhr_table_info["ranges"]["Below Average"],
|
||||
rhr_table_info["ranges"]["Average"],
|
||||
rhr_table_info["ranges"]["Above Average"],
|
||||
rhr_table_info["ranges"]["Good"],
|
||||
rhr_table_info["ranges"]["Excellent"],
|
||||
rhr_table_info["ranges"]["Athlete"],
|
||||
]
|
||||
]
|
||||
|
||||
contexts["page_12"]["rhr_table"] = (
|
||||
graph_generator.generate_resting_heart_rate_table(
|
||||
data=rhr_data,
|
||||
columns=rhr_columns,
|
||||
rhr_value=rhr_value,
|
||||
category=category,
|
||||
save_as_base64=True,
|
||||
)
|
||||
)
|
||||
|
||||
# Page 13 - Muscle Oxygenation (previously page 12)
|
||||
contexts["page_13"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": 13,
|
||||
}
|
||||
|
||||
# Generate muscle oxygenation chart if data is available
|
||||
if graph_generator and self.oxygenation_df is not None:
|
||||
try:
|
||||
chart_str, metrics = graph_generator.generate_muscle_oxygenation_chart(
|
||||
self.oxygenation_df, save_as_base64=True
|
||||
)
|
||||
contexts["page_13"]["muscle_oxygenation_chart"] = chart_str
|
||||
contexts["page_13"].update(metrics)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not generate muscle oxygenation chart: {e}")
|
||||
|
||||
# Pages 14-18 (previously 13-17)
|
||||
for i in range(1, 6):
|
||||
contexts[f"page_{i + 13}"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": i + 12,
|
||||
"page_number": i + 13,
|
||||
}
|
||||
|
||||
# Page 18 - Glossary with Body Fat Percentage Master Chart
|
||||
contexts["page_18"] = {
|
||||
# Page 19 - Glossary with Body Fat Percentage Master Chart (previously page 18)
|
||||
contexts["page_19"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": 18,
|
||||
"page_number": 19,
|
||||
"body_fat_percentage_chart": graphs.get(
|
||||
"body_fat_percentage_master_chart", ""
|
||||
),
|
||||
}
|
||||
|
||||
# Page 19
|
||||
contexts["page_19"] = {
|
||||
# Page 20 (previously page 19)
|
||||
contexts["page_20"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": 19,
|
||||
"page_number": 20,
|
||||
}
|
||||
|
||||
return contexts
|
||||
|
||||
@@ -6,6 +6,7 @@ Based on the analysis notebooks in services_dfdf/.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import io
|
||||
from pathlib import Path
|
||||
|
||||
import matplotlib
|
||||
@@ -1305,3 +1306,611 @@ class GraphGenerator:
|
||||
plt.close()
|
||||
|
||||
return self._image_to_base64(chart_path) if save_as_base64 else str(chart_path)
|
||||
|
||||
def generate_vo2_max_table(
|
||||
self,
|
||||
data: list[list],
|
||||
columns: list[str],
|
||||
vo2_max_value: float = None,
|
||||
category: str = None,
|
||||
cell_colors: list[list[str]] = None,
|
||||
save_as_base64: bool = True,
|
||||
) -> str:
|
||||
"""
|
||||
Generate VO2 Max table as an image with optimized sizing, highlighting the patient's category.
|
||||
|
||||
Args:
|
||||
data: List of rows (each row is a list of values)
|
||||
columns: List of column headers
|
||||
vo2_max_value: Patient's VO2 max value (for title and arrow)
|
||||
category: Category that the patient falls into (e.g., 'Good', 'Excellent')
|
||||
cell_colors: Optional matrix of cell colors
|
||||
save_as_base64: If True, return base64 string
|
||||
|
||||
Returns:
|
||||
Base64 string or file path
|
||||
"""
|
||||
import io
|
||||
|
||||
from matplotlib.patches import FancyArrowPatch, RegularPolygon
|
||||
|
||||
# Fixed optimal sizing for VO2 Max table (7 columns, 1 data row)
|
||||
fig, ax = plt.subplots(figsize=(14, 2.2))
|
||||
ax.axis("off")
|
||||
|
||||
# Create table
|
||||
table = ax.table(
|
||||
cellText=data,
|
||||
colLabels=columns,
|
||||
cellLoc="center",
|
||||
loc="center",
|
||||
bbox=[0, 0, 1, 1],
|
||||
)
|
||||
|
||||
# Style the table
|
||||
table.auto_set_font_size(False)
|
||||
table.set_fontsize(11)
|
||||
table.scale(1, 1.8)
|
||||
|
||||
# Header row styling (cyan background)
|
||||
for i in range(len(columns)):
|
||||
cell = table[(0, i)]
|
||||
cell.set_facecolor("#7dd3fc") # cyan-300 equivalent
|
||||
cell.set_text_props(weight="bold", color="black", fontsize=12)
|
||||
cell.set_edgecolor("#9ca3af") # gray-400
|
||||
cell.set_linewidth(1)
|
||||
|
||||
# Find the column index for the category (if provided)
|
||||
category_index = None
|
||||
if category and category in columns:
|
||||
category_index = columns.index(category)
|
||||
|
||||
# Data row styling
|
||||
for i in range(len(data[0])):
|
||||
cell = table[(1, i)]
|
||||
if i == 0: # Age column
|
||||
cell.set_facecolor("#a5f3fc") # cyan-200
|
||||
cell.set_text_props(weight="semibold", color="black", fontsize=11)
|
||||
else:
|
||||
cell.set_facecolor("#f3f4f6") # gray-100
|
||||
cell.set_text_props(color="black", fontsize=10)
|
||||
# Bold the cell that corresponds to the patient's category
|
||||
if category_index is not None and i == category_index:
|
||||
cell.set_text_props(weight="bold", color="black", fontsize=11)
|
||||
cell.set_edgecolor("#9ca3af") # gray-400
|
||||
cell.set_linewidth(1)
|
||||
|
||||
# Add arrow indicator below the category column
|
||||
if category_index is not None:
|
||||
# Calculate position
|
||||
cell_width = 1.0 / len(columns)
|
||||
arrow_x = (category_index + 0.5) * cell_width
|
||||
|
||||
# Draw arrow pointing up
|
||||
arrow = FancyArrowPatch(
|
||||
(arrow_x, -0.15),
|
||||
(arrow_x, -0.05),
|
||||
arrowstyle="->",
|
||||
mutation_scale=20,
|
||||
linewidth=2,
|
||||
color="black",
|
||||
transform=ax.transAxes,
|
||||
)
|
||||
ax.add_patch(arrow)
|
||||
|
||||
# Add triangle at the top
|
||||
triangle = RegularPolygon(
|
||||
(arrow_x, -0.05),
|
||||
3,
|
||||
radius=0.02,
|
||||
orientation=np.pi / 2,
|
||||
color="black",
|
||||
transform=ax.transAxes,
|
||||
)
|
||||
ax.add_patch(triangle)
|
||||
|
||||
# Set title - calculate approximate percentile
|
||||
if vo2_max_value is not None:
|
||||
if category == "Superior":
|
||||
percentile = "100th percentile"
|
||||
else:
|
||||
percentile_map = {
|
||||
"Very Poor": "1st-10th percentile",
|
||||
"Poor": "10th-20th percentile",
|
||||
"Fair": "20th-40th percentile",
|
||||
"Good": "40th-60th percentile",
|
||||
"Excellent": "60th-80th percentile",
|
||||
}
|
||||
percentile = percentile_map.get(category, "N/A")
|
||||
|
||||
title = f"VO2 Max - {vo2_max_value:.1f} ({percentile})"
|
||||
ax.set_title(title, fontsize=14, fontweight="bold", pad=10)
|
||||
|
||||
if save_as_base64:
|
||||
buf = io.BytesIO()
|
||||
plt.savefig(
|
||||
buf,
|
||||
format="png",
|
||||
bbox_inches="tight",
|
||||
dpi=300,
|
||||
facecolor="white",
|
||||
pad_inches=0.05,
|
||||
)
|
||||
plt.close(fig)
|
||||
buf.seek(0)
|
||||
return base64.b64encode(buf.read()).decode("utf-8")
|
||||
else:
|
||||
output_path = (
|
||||
self.charts_dir / f"vo2_max_table_{pd.Timestamp.now().timestamp()}.png"
|
||||
)
|
||||
plt.savefig(
|
||||
output_path,
|
||||
bbox_inches="tight",
|
||||
dpi=300,
|
||||
facecolor="white",
|
||||
pad_inches=0.05,
|
||||
)
|
||||
plt.close(fig)
|
||||
return str(output_path)
|
||||
|
||||
def generate_heart_rate_zones_table(
|
||||
self,
|
||||
data: list[list],
|
||||
columns: list[str],
|
||||
cell_colors: list[list[str]] = None,
|
||||
save_as_base64: bool = True,
|
||||
) -> str:
|
||||
"""
|
||||
Generate Heart Rate Zones table as an image with optimized sizing.
|
||||
|
||||
Args:
|
||||
data: List of rows (each row is a list of values)
|
||||
columns: List of column headers (Zone 1-5)
|
||||
cell_colors: Optional matrix of cell colors (IGNORED - using notebook colors)
|
||||
save_as_base64: If True, return base64 string
|
||||
|
||||
Returns:
|
||||
Base64 string or file path
|
||||
"""
|
||||
import io
|
||||
|
||||
# Optimal sizing for HR Zones table (5 columns, 8 rows) - match notebook exactly
|
||||
fig, ax = plt.subplots(figsize=(12, 8))
|
||||
ax.axis("off")
|
||||
|
||||
# Data comes pre-formatted with newlines from context_generator - use as-is
|
||||
# No text wrapping needed
|
||||
|
||||
# Create table without rowLabels - match notebook exactly
|
||||
table = ax.table(
|
||||
cellText=data,
|
||||
colLabels=columns,
|
||||
loc="center",
|
||||
cellLoc="center",
|
||||
)
|
||||
|
||||
# Style the table - match notebook exactly
|
||||
table.auto_set_font_size(False)
|
||||
table.set_fontsize(10)
|
||||
table.scale(1, 3.5) # Increased vertical scale for multi-line text
|
||||
|
||||
# Header row styling
|
||||
for j, label in enumerate(columns):
|
||||
cell = table[(0, j)]
|
||||
cell.set_facecolor("#7dd3fc") # cyan-300
|
||||
cell.set_text_props(weight="bold")
|
||||
|
||||
# Row specific styling - match notebook colors exactly
|
||||
colors = ["#fecaca", "#fecaca", "#fef08a", "#bbf7d0", "#bbf7d0"]
|
||||
|
||||
# HR BPM row is at index 2 (0-based in data) -> row 3 in table (0 is header)
|
||||
for j in range(len(columns)):
|
||||
cell = table[(3, j)]
|
||||
cell.set_facecolor(colors[j])
|
||||
cell.set_text_props(weight="bold")
|
||||
|
||||
# Breathing row is at index 7 -> row 8 in table
|
||||
for j in range(len(columns)):
|
||||
cell = table[(8, j)]
|
||||
cell.set_facecolor(colors[j])
|
||||
cell.set_text_props(weight="bold")
|
||||
|
||||
# Add title matching notebook
|
||||
plt.title(
|
||||
"Personalized Heart Rate Zones", fontsize=16, fontweight="bold", pad=5
|
||||
)
|
||||
plt.tight_layout()
|
||||
|
||||
if save_as_base64:
|
||||
buf = io.BytesIO()
|
||||
plt.savefig(
|
||||
buf,
|
||||
format="png",
|
||||
bbox_inches="tight",
|
||||
dpi=300,
|
||||
facecolor="white",
|
||||
)
|
||||
plt.close(fig)
|
||||
buf.seek(0)
|
||||
return base64.b64encode(buf.read()).decode("utf-8")
|
||||
else:
|
||||
output_path = (
|
||||
self.charts_dir / f"hr_zones_table_{pd.Timestamp.now().timestamp()}.png"
|
||||
)
|
||||
plt.savefig(
|
||||
output_path,
|
||||
bbox_inches="tight",
|
||||
dpi=300,
|
||||
facecolor="white",
|
||||
)
|
||||
plt.close(fig)
|
||||
return str(output_path)
|
||||
|
||||
def generate_resting_heart_rate_table(
|
||||
self,
|
||||
data: list[list],
|
||||
columns: list[str],
|
||||
rhr_value: float = None,
|
||||
category: str = None,
|
||||
cell_colors: list[list[str]] = None,
|
||||
save_as_base64: bool = True,
|
||||
) -> str:
|
||||
"""
|
||||
Generate Resting Heart Rate table as an image with optimized sizing, highlighting the patient's category.
|
||||
|
||||
Args:
|
||||
data: List of rows (each row is a list of values)
|
||||
columns: List of column headers
|
||||
rhr_value: Patient's resting heart rate value in bpm (for title and arrow)
|
||||
category: Category that the patient falls into (e.g., 'Good', 'Excellent')
|
||||
cell_colors: Optional matrix of cell colors
|
||||
save_as_base64: If True, return base64 string
|
||||
|
||||
Returns:
|
||||
Base64 string or file path
|
||||
"""
|
||||
import io
|
||||
|
||||
from matplotlib.patches import FancyArrowPatch, RegularPolygon
|
||||
|
||||
# Optimal sizing for RHR table (8 columns, 1 data row)
|
||||
fig, ax = plt.subplots(figsize=(16, 2.2))
|
||||
ax.axis("off")
|
||||
|
||||
# Create table
|
||||
table = ax.table(
|
||||
cellText=data,
|
||||
colLabels=columns,
|
||||
cellLoc="center",
|
||||
loc="center",
|
||||
bbox=[0, 0, 1, 1],
|
||||
)
|
||||
|
||||
# Style the table
|
||||
table.auto_set_font_size(False)
|
||||
table.set_fontsize(11)
|
||||
table.scale(1, 1.8)
|
||||
|
||||
# Header row styling (cyan background)
|
||||
for i in range(len(columns)):
|
||||
cell = table[(0, i)]
|
||||
cell.set_facecolor("#7dd3fc") # cyan-300 equivalent
|
||||
cell.set_text_props(weight="bold", color="black", fontsize=12)
|
||||
cell.set_edgecolor("#9ca3af") # gray-400
|
||||
cell.set_linewidth(1)
|
||||
|
||||
# Find the column index for the category (if provided)
|
||||
category_index = None
|
||||
if category and category in columns:
|
||||
category_index = columns.index(category)
|
||||
|
||||
# Data row styling
|
||||
for i in range(len(data[0])):
|
||||
cell = table[(1, i)]
|
||||
if i == 0: # Age column
|
||||
cell.set_facecolor("#a5f3fc") # cyan-200
|
||||
cell.set_text_props(weight="semibold", color="black", fontsize=11)
|
||||
else:
|
||||
# Highlight the category cell with light green background
|
||||
if category_index is not None and i == category_index:
|
||||
cell.set_facecolor("#d1fae5") # green-200 equivalent
|
||||
cell.set_text_props(weight="bold", color="black", fontsize=11)
|
||||
else:
|
||||
cell.set_facecolor("#f3f4f6") # gray-100
|
||||
cell.set_text_props(color="black", fontsize=10)
|
||||
cell.set_edgecolor("#9ca3af") # gray-400
|
||||
cell.set_linewidth(1)
|
||||
|
||||
# Add arrow indicator below the category column
|
||||
if category_index is not None:
|
||||
# Calculate position
|
||||
cell_width = 1.0 / len(columns)
|
||||
arrow_x = (category_index + 0.5) * cell_width
|
||||
|
||||
# Draw arrow pointing up
|
||||
arrow = FancyArrowPatch(
|
||||
(arrow_x, -0.15),
|
||||
(arrow_x, -0.05),
|
||||
arrowstyle="->",
|
||||
mutation_scale=20,
|
||||
linewidth=2,
|
||||
color="black",
|
||||
transform=ax.transAxes,
|
||||
)
|
||||
ax.add_patch(arrow)
|
||||
|
||||
# Add triangle at the top
|
||||
triangle = RegularPolygon(
|
||||
(arrow_x, -0.05),
|
||||
3,
|
||||
radius=0.02,
|
||||
orientation=np.pi / 2,
|
||||
color="black",
|
||||
transform=ax.transAxes,
|
||||
)
|
||||
ax.add_patch(triangle)
|
||||
|
||||
# Set title
|
||||
if rhr_value is not None:
|
||||
title = f"Resting Heart Rate - {rhr_value:.0f}bpm"
|
||||
ax.set_title(title, fontsize=14, fontweight="bold", pad=10)
|
||||
|
||||
if save_as_base64:
|
||||
buf = io.BytesIO()
|
||||
plt.savefig(
|
||||
buf,
|
||||
format="png",
|
||||
bbox_inches="tight",
|
||||
dpi=300,
|
||||
facecolor="white",
|
||||
pad_inches=0.05,
|
||||
)
|
||||
plt.close(fig)
|
||||
buf.seek(0)
|
||||
return base64.b64encode(buf.read()).decode("utf-8")
|
||||
else:
|
||||
output_path = (
|
||||
self.charts_dir / f"rhr_table_{pd.Timestamp.now().timestamp()}.png"
|
||||
)
|
||||
plt.savefig(
|
||||
output_path,
|
||||
bbox_inches="tight",
|
||||
dpi=300,
|
||||
facecolor="white",
|
||||
pad_inches=0.05,
|
||||
)
|
||||
plt.close(fig)
|
||||
return str(output_path)
|
||||
|
||||
def generate_muscle_oxygenation_chart(
|
||||
self, oxygenation_df: pd.DataFrame, save_as_base64: bool = True
|
||||
) -> tuple:
|
||||
"""
|
||||
Generate comprehensive muscle oxygenation (SmO2) chart with both legs and heart rate.
|
||||
|
||||
Args:
|
||||
oxygenation_df: DataFrame with muscle oxygenation data (Train.Red CSV format)
|
||||
save_as_base64: If True, return base64 string, else return file path
|
||||
|
||||
Returns:
|
||||
Tuple of (chart_string, metrics_dict) where metrics_dict contains key values
|
||||
"""
|
||||
# Data preparation
|
||||
df_oxy = oxygenation_df.copy()
|
||||
|
||||
# Convert columns to numeric
|
||||
df_oxy["Timestamp (seconds passed)"] = pd.to_numeric(
|
||||
df_oxy["Timestamp (seconds passed)"], errors="coerce"
|
||||
)
|
||||
df_oxy["Left_SmO2"] = pd.to_numeric(df_oxy["SmO2"], errors="coerce")
|
||||
df_oxy["Right_SmO2"] = pd.to_numeric(df_oxy["SmO2.1"], errors="coerce")
|
||||
df_oxy["Heart_Rate"] = pd.to_numeric(
|
||||
df_oxy["Heart Rate (BPM)"], errors="coerce"
|
||||
)
|
||||
df_oxy["Lap"] = pd.to_numeric(df_oxy["Lap/Event"], errors="coerce")
|
||||
|
||||
# Drop rows with missing timestamps
|
||||
df_oxy = df_oxy.dropna(subset=["Timestamp (seconds passed)"])
|
||||
df_oxy = df_oxy.sort_values("Timestamp (seconds passed)").reset_index(drop=True)
|
||||
|
||||
# Apply 10-second rolling mean smoothing
|
||||
time_diffs = df_oxy["Timestamp (seconds passed)"].diff().dropna()
|
||||
avg_sampling_interval = time_diffs.median()
|
||||
sampling_freq = 1 / avg_sampling_interval if avg_sampling_interval > 0 else 10
|
||||
window_samples = int(10 * sampling_freq)
|
||||
|
||||
df_oxy["Left_SmO2_smooth"] = (
|
||||
df_oxy["Left_SmO2"]
|
||||
.rolling(window=window_samples, center=True, min_periods=1)
|
||||
.mean()
|
||||
)
|
||||
df_oxy["Right_SmO2_smooth"] = (
|
||||
df_oxy["Right_SmO2"]
|
||||
.rolling(window=window_samples, center=True, min_periods=1)
|
||||
.mean()
|
||||
)
|
||||
df_oxy["Heart_Rate_smooth"] = (
|
||||
df_oxy["Heart_Rate"]
|
||||
.rolling(window=window_samples, center=True, min_periods=1)
|
||||
.mean()
|
||||
)
|
||||
|
||||
# Identify stage boundaries
|
||||
lap_changes = df_oxy[df_oxy["Lap"].diff() != 0].copy()
|
||||
lap_starts = {}
|
||||
for idx, row in lap_changes.iterrows():
|
||||
lap_num = int(row["Lap"])
|
||||
lap_starts[lap_num] = row["Timestamp (seconds passed)"]
|
||||
|
||||
warm_up_end = lap_starts.get(1, df_oxy["Timestamp (seconds passed)"].max())
|
||||
recovery_start = lap_starts.get(7, df_oxy["Timestamp (seconds passed)"].max())
|
||||
|
||||
# Calculate recovery percentages
|
||||
warm_up_last_30_start = warm_up_end - 30
|
||||
warm_up_mask = (
|
||||
df_oxy["Timestamp (seconds passed)"] >= warm_up_last_30_start
|
||||
) & (df_oxy["Timestamp (seconds passed)"] <= warm_up_end)
|
||||
|
||||
recovery_end = df_oxy["Timestamp (seconds passed)"].max()
|
||||
recovery_last_30_start = recovery_end - 30
|
||||
recovery_mask = (
|
||||
df_oxy["Timestamp (seconds passed)"] >= recovery_last_30_start
|
||||
) & (df_oxy["Timestamp (seconds passed)"] <= recovery_end)
|
||||
|
||||
left_warmup_avg = df_oxy.loc[warm_up_mask, "Left_SmO2_smooth"].mean()
|
||||
left_recovery_avg = df_oxy.loc[recovery_mask, "Left_SmO2_smooth"].mean()
|
||||
left_recovery_pct = round((left_recovery_avg / left_warmup_avg) * 100)
|
||||
|
||||
right_warmup_avg = df_oxy.loc[warm_up_mask, "Right_SmO2_smooth"].mean()
|
||||
right_recovery_avg = df_oxy.loc[recovery_mask, "Right_SmO2_smooth"].mean()
|
||||
right_recovery_pct = round((right_recovery_avg / right_warmup_avg) * 100)
|
||||
|
||||
# Calculate key metrics
|
||||
active_mask = (df_oxy["Timestamp (seconds passed)"] >= warm_up_end) & (
|
||||
df_oxy["Timestamp (seconds passed)"] <= recovery_start
|
||||
)
|
||||
active_data = df_oxy[active_mask]
|
||||
|
||||
left_min = active_data["Left_SmO2_smooth"].min()
|
||||
left_min_lap = int(
|
||||
active_data.loc[active_data["Left_SmO2_smooth"].idxmin(), "Lap"]
|
||||
)
|
||||
right_min = active_data["Right_SmO2_smooth"].min()
|
||||
right_min_lap = int(
|
||||
active_data.loc[active_data["Right_SmO2_smooth"].idxmin(), "Lap"]
|
||||
)
|
||||
|
||||
left_drop = left_warmup_avg - left_min
|
||||
right_drop = right_warmup_avg - right_min
|
||||
|
||||
hr_warmup = df_oxy[df_oxy["Timestamp (seconds passed)"] <= warm_up_end][
|
||||
"Heart_Rate_smooth"
|
||||
].mean()
|
||||
hr_max = active_data["Heart_Rate_smooth"].max()
|
||||
|
||||
# Create the plot
|
||||
fig, ax1 = plt.subplots(figsize=(18, 8))
|
||||
|
||||
time = df_oxy["Timestamp (seconds passed)"]
|
||||
ax1.plot(
|
||||
time,
|
||||
df_oxy["Left_SmO2_smooth"],
|
||||
label=f"Left SmO₂ (Rec {left_recovery_pct}% of warm-up)",
|
||||
color="#2E86AB",
|
||||
linewidth=2,
|
||||
)
|
||||
ax1.plot(
|
||||
time,
|
||||
df_oxy["Right_SmO2_smooth"],
|
||||
label=f"Right SmO₂ (Rec {right_recovery_pct}% of warm-up)",
|
||||
color="#A23B72",
|
||||
linewidth=2,
|
||||
)
|
||||
|
||||
ax1.set_xlabel("Time (seconds)", fontsize=12, fontweight="bold")
|
||||
ax1.set_ylabel("SmO₂ (%)", fontsize=12, fontweight="bold")
|
||||
ax1.tick_params(axis="y", labelcolor="black")
|
||||
ax1.grid(True, alpha=0.3, linestyle="--")
|
||||
|
||||
# Add secondary axis for heart rate
|
||||
ax2 = ax1.twinx()
|
||||
ax2.plot(
|
||||
time,
|
||||
df_oxy["Heart_Rate_smooth"],
|
||||
label="Heart Rate",
|
||||
color="red",
|
||||
linewidth=1.5,
|
||||
linestyle="--",
|
||||
alpha=0.7,
|
||||
)
|
||||
ax2.set_ylabel("Heart Rate (BPM)", fontsize=12, fontweight="bold", color="red")
|
||||
ax2.tick_params(axis="y", labelcolor="red")
|
||||
|
||||
# Add shaded regions
|
||||
ax1.axvspan(0, warm_up_end, alpha=0.15, color="blue", label="Warm-up")
|
||||
|
||||
active_laps = [1, 2, 3, 4, 5, 6]
|
||||
colors_active = ["yellow", "orange"] * 3
|
||||
for i, lap in enumerate(active_laps):
|
||||
start = lap_starts.get(lap, 0)
|
||||
end = lap_starts.get(lap + 1, recovery_start) if lap < 6 else recovery_start
|
||||
ax1.axvspan(start, end, alpha=0.1, color=colors_active[i])
|
||||
|
||||
ax1.axvspan(
|
||||
recovery_start, recovery_end, alpha=0.2, color="gray", label="Recovery"
|
||||
)
|
||||
ax1.axvline(
|
||||
x=recovery_start, color="black", linestyle="-", linewidth=2, alpha=0.7
|
||||
)
|
||||
|
||||
# Add lap labels
|
||||
for lap in range(1, 7):
|
||||
start = lap_starts.get(lap, 0)
|
||||
end = lap_starts.get(lap + 1, recovery_start) if lap < 6 else recovery_start
|
||||
mid = (start + end) / 2
|
||||
ax1.text(
|
||||
mid,
|
||||
ax1.get_ylim()[1] * 0.97,
|
||||
f"Lap {lap}",
|
||||
ha="center",
|
||||
va="top",
|
||||
fontsize=10,
|
||||
fontweight="bold",
|
||||
bbox=dict(boxstyle="round,pad=0.3", facecolor="white", alpha=0.7),
|
||||
)
|
||||
|
||||
plt.title(
|
||||
"Train.Red SmO₂ Ramp - Muscle Oxygenation Analysis",
|
||||
fontsize=16,
|
||||
fontweight="bold",
|
||||
pad=20,
|
||||
)
|
||||
|
||||
# Combine legends
|
||||
lines1, labels1 = ax1.get_legend_handles_labels()
|
||||
lines2, labels2 = ax2.get_legend_handles_labels()
|
||||
ax1.legend(
|
||||
lines1 + lines2,
|
||||
labels1 + labels2,
|
||||
loc="upper left",
|
||||
fontsize=10,
|
||||
framealpha=0.9,
|
||||
)
|
||||
|
||||
plt.tight_layout()
|
||||
|
||||
# Prepare metrics dictionary
|
||||
metrics = {
|
||||
"left_baseline_smo2": f"{left_warmup_avg:.1f}%",
|
||||
"right_baseline_smo2": f"{right_warmup_avg:.1f}%",
|
||||
"left_minimum_smo2": f"{left_min:.1f}%",
|
||||
"right_minimum_smo2": f"{right_min:.1f}%",
|
||||
"left_minimum_lap": f"Lap {left_min_lap}",
|
||||
"right_minimum_lap": f"Lap {right_min_lap}",
|
||||
"left_oxygen_drop": f"{left_drop:.1f}%",
|
||||
"right_oxygen_drop": f"{right_drop:.1f}%",
|
||||
"left_drop_percentage": f"{(left_drop / left_warmup_avg * 100):.0f}% decrease",
|
||||
"right_drop_percentage": f"{(right_drop / right_warmup_avg * 100):.0f}% decrease",
|
||||
"left_recovery_percentage": f"{left_recovery_pct}%",
|
||||
"right_recovery_percentage": f"{right_recovery_pct}%",
|
||||
"hr_warmup": f"{hr_warmup:.0f}",
|
||||
"hr_max": f"{hr_max:.0f}",
|
||||
"test_duration": f"~{(recovery_start - warm_up_end) / 60:.0f} minutes active test",
|
||||
"recovery_assessment": "Excellent recovery capacity"
|
||||
if (left_recovery_pct + right_recovery_pct) / 2 >= 100
|
||||
else "Good recovery capacity",
|
||||
}
|
||||
|
||||
# Save or return
|
||||
if save_as_base64:
|
||||
buf = io.BytesIO()
|
||||
plt.savefig(buf, format="png", dpi=300, bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
buf.seek(0)
|
||||
chart_str = base64.b64encode(buf.read()).decode("utf-8")
|
||||
return chart_str, metrics
|
||||
else:
|
||||
output_path = self.charts_dir / "muscle_oxygenation_chart.png"
|
||||
plt.savefig(output_path, dpi=300, bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
return str(output_path), metrics
|
||||
|
||||
@@ -260,6 +260,13 @@ class ReportGeneratorService:
|
||||
.chart-large {{
|
||||
max-height: 500px !important;
|
||||
}}
|
||||
.table-image {{
|
||||
max-height: none !important;
|
||||
width: auto !important;
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
object-fit: contain;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body class="m-0 p-0">
|
||||
@@ -421,6 +428,21 @@ class ReportGeneratorService:
|
||||
)
|
||||
graphs_dict["body_fat_percentage_master_chart"] = ""
|
||||
|
||||
# Load static fuelling analysis flowchart for page 10
|
||||
flowchart_path = Path("app/estimated_carb_storage.png")
|
||||
if flowchart_path.exists():
|
||||
try:
|
||||
with open(flowchart_path, "rb") as f:
|
||||
graphs_dict["fuelling_analysis_flowchart"] = base64.b64encode(
|
||||
f.read()
|
||||
).decode("utf-8")
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not load fuelling analysis flowchart: {e}")
|
||||
graphs_dict["fuelling_analysis_flowchart"] = ""
|
||||
else:
|
||||
print(f"Warning: Fuelling analysis flowchart not found at {flowchart_path}")
|
||||
graphs_dict["fuelling_analysis_flowchart"] = ""
|
||||
|
||||
# Generate spirometry chart
|
||||
print("Step 4: Generating spirometry chart...")
|
||||
try:
|
||||
@@ -496,6 +518,7 @@ class ReportGeneratorService:
|
||||
pnoe_csv_path,
|
||||
str(spirometry_csv_path),
|
||||
None, # No SECA file
|
||||
oxygenation_csv_path, # Pass oxygenation CSV path
|
||||
)
|
||||
# Set patient info manually
|
||||
self.context_generator.patient_info = {
|
||||
@@ -507,7 +530,10 @@ class ReportGeneratorService:
|
||||
"gender": gender,
|
||||
}
|
||||
contexts = self.context_generator.generate_all_contexts(
|
||||
patient_name, graphs_dict, metric_overrides=metric_overrides
|
||||
patient_name,
|
||||
graphs_dict,
|
||||
metric_overrides=metric_overrides,
|
||||
graph_generator=self.graph_generator,
|
||||
)
|
||||
|
||||
# Step 5: Calculate analysis metrics
|
||||
|
||||
+15
-15
@@ -2,7 +2,7 @@
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"execution_count": null,
|
||||
"id": "b18c1027",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -88,7 +88,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": null,
|
||||
"id": "56a9d655",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -122,7 +122,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"execution_count": null,
|
||||
"id": "990f4b4f",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -146,7 +146,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"execution_count": null,
|
||||
"id": "041cbc3d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -195,7 +195,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"execution_count": null,
|
||||
"id": "de7cadd1",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -214,7 +214,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"execution_count": null,
|
||||
"id": "cb972ed3",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -325,7 +325,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"execution_count": null,
|
||||
"id": "98d9295a",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -345,7 +345,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"execution_count": null,
|
||||
"id": "cdfeb309",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -398,7 +398,7 @@
|
||||
"optimal_fat_idx = df['fat_carb_ratio'].idxmax()\n",
|
||||
"optimal_row = df.loc[optimal_fat_idx]\n",
|
||||
"\n",
|
||||
"print(f\"Optimal Fat Burning Zone (highest fat:carb ratio):\")\n",
|
||||
"print(\"Optimal Fat Burning Zone (highest fat:carb ratio):\")\n",
|
||||
"print(f\"Time: {optimal_row['T(sec)']} seconds\")\n",
|
||||
"print(f\"Fat burn rate: {optimal_row['FAT_smoothed']:.3f} kcal/min\")\n",
|
||||
"print(f\"Carb burn rate: {optimal_row['CHO_smoothed']:.3f} kcal/min\")\n",
|
||||
@@ -409,7 +409,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"execution_count": null,
|
||||
"id": "4420cfea",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -467,7 +467,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 21,
|
||||
"execution_count": null,
|
||||
"id": "62803668",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -552,7 +552,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"execution_count": null,
|
||||
"id": "07593b56",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -590,7 +590,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"execution_count": null,
|
||||
"id": "c90415b2",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -653,7 +653,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"execution_count": null,
|
||||
"id": "c3b2cc59",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -742,7 +742,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"execution_count": null,
|
||||
"id": "672d68f3",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
|
||||
+2311
-37
File diff suppressed because one or more lines are too long
@@ -0,0 +1,18 @@
|
||||
Metric,Value
|
||||
Left Baseline SmO2 (%),75.37
|
||||
Right Baseline SmO2 (%),82.91
|
||||
Left Minimum SmO2 (%),69.34
|
||||
Right Minimum SmO2 (%),73.65
|
||||
Left Maximum SmO2 (%),78.24
|
||||
Right Maximum SmO2 (%),82.59
|
||||
Left Recovery SmO2 (%),82.47
|
||||
Right Recovery SmO2 (%),80.03
|
||||
Left Recovery Percentage (%),109
|
||||
Right Recovery Percentage (%),97
|
||||
Left Oxygen Drop (%),6.03
|
||||
Right Oxygen Drop (%),9.26
|
||||
Warmup HR (bpm),93.2
|
||||
Maximum HR (bpm),168.2
|
||||
Recovery HR (bpm),107.7
|
||||
Test Duration (seconds),1287
|
||||
Recovery Duration (seconds),159
|
||||
|
@@ -0,0 +1,192 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
# --- CONFIGURATION TABLES (From your PDFs) ---
|
||||
|
||||
# From deficit.pdf
|
||||
ACTIVITY_MULTIPLIERS = {
|
||||
"Sedentary": 1.2, "Light": 1.375, "Moderate": 1.55, "Active": 1.7, "Extreme": 1.9
|
||||
}
|
||||
|
||||
# From deficit.pdf (Weight Loss kg -> Calorie Deficit)
|
||||
DEFICIT_TABLE = {
|
||||
0.1: 85, 0.2: 169, 0.3: 254, 0.4: 339, 0.5: 423,
|
||||
0.6: 508, 0.7: 593, 0.8: 677, 0.9: 762, 1.0: 847,
|
||||
1.1: 931, 1.2: 1016
|
||||
}
|
||||
|
||||
# From no_deficit.pdf (Protein Multipliers g/kg of Lean Body Mass)
|
||||
PROTEIN_GUIDELINES = {
|
||||
(0, 30): {'maintenance': 1.9, 'deficit': 2.3},
|
||||
(30, 40): {'maintenance': 2.15, 'deficit': 2.6},
|
||||
(40, 50): {'maintenance': 2.45, 'deficit': 2.95},
|
||||
(50, 60): {'maintenance': 2.75, 'deficit': 3.3},
|
||||
(60, 100): {'maintenance': 3.05, 'deficit': 3.65}
|
||||
}
|
||||
|
||||
def analyze_pnoe_data(csv_path):
|
||||
"""
|
||||
Parses PNOE CSV. FIX: Uses MEDIAN instead of MEAN to avoid outliers.
|
||||
"""
|
||||
df = pd.read_csv(csv_path, delimiter=';')
|
||||
df.columns = df.columns.str.strip()
|
||||
|
||||
# Filter for RMR window (assumed T=60s to T=300s, 4 minutes of stable rest)
|
||||
df_stable = df[(df['T(sec)'] >= 60) & (df['T(sec)'] <= 300)].copy()
|
||||
|
||||
# Ensure data columns are numeric
|
||||
for col in ['EE(kcal/day)', 'RER', 'T(sec)']:
|
||||
df_stable.loc[:, col] = pd.to_numeric(df_stable[col], errors='coerce')
|
||||
|
||||
df_stable.dropna(subset=['EE(kcal/day)', 'RER'], inplace=True)
|
||||
|
||||
if not df_stable.empty:
|
||||
# **CRITICAL CHANGE: Use Median instead of Mean**
|
||||
rmr_measured = df_stable['EE(kcal/day)'].median()
|
||||
rer = df_stable['RER'].median()
|
||||
else:
|
||||
# Fallback if window is empty
|
||||
rmr_measured = 1386.0
|
||||
rer = 0.85
|
||||
|
||||
# Calculate Fuel Source
|
||||
clamped_rer = max(0.7, min(1.0, rer))
|
||||
percent_carbs = (clamped_rer - 0.7) / 0.3
|
||||
percent_fat = 1.0 - percent_carbs
|
||||
|
||||
return {
|
||||
"measured_rmr": int(round(rmr_measured)),
|
||||
"rer": round(rer, 2),
|
||||
"fuel_source": {
|
||||
"fat_percent": round(percent_fat * 100, 1),
|
||||
"carb_percent": round(percent_carbs * 100, 1)
|
||||
}
|
||||
}
|
||||
|
||||
def assess_metabolic_health(measured_rmr, weight_kg, height_cm, age, sex):
|
||||
"""
|
||||
Calculates Predicted RMR (Mifflin-St Jeor) and compares to Measured RMR.
|
||||
"""
|
||||
# Mifflin-St Jeor Formula
|
||||
if sex.lower() == 'male':
|
||||
predicted_rmr = (10 * weight_kg) + (6.25 * height_cm) - (5 * age) + 5
|
||||
else:
|
||||
predicted_rmr = (10 * weight_kg) + (6.25 * height_cm) - (5 * age) - 161
|
||||
|
||||
variance = ((measured_rmr - predicted_rmr) / predicted_rmr) * 100
|
||||
|
||||
# Interpretation
|
||||
if variance > 10:
|
||||
metabolism_type = "Fast"
|
||||
elif variance < -10:
|
||||
metabolism_type = "Slow"
|
||||
else:
|
||||
metabolism_type = "Normal"
|
||||
|
||||
return {
|
||||
"predicted_rmr_mifflin": int(round(predicted_rmr)),
|
||||
"variance_percent": round(variance, 1),
|
||||
"metabolism_type": metabolism_type
|
||||
}
|
||||
|
||||
def generate_nutrition_plan(measured_rmr, weight_kg, body_fat_percent, age, activity_level, weekly_weight_loss_goal_kg):
|
||||
"""
|
||||
Calculates TDEE, applies Deficit, and calculates Macros based on uploaded PDFs.
|
||||
"""
|
||||
# 1. TDEE (Maintenance Calories)
|
||||
multiplier = ACTIVITY_MULTIPLIERS.get(activity_level, 1.2)
|
||||
maintenance_calories = measured_rmr * multiplier
|
||||
|
||||
# 2. Daily Calorie Target
|
||||
daily_deficit = DEFICIT_TABLE.get(weekly_weight_loss_goal_kg, 0)
|
||||
target_calories = maintenance_calories - daily_deficit
|
||||
is_deficit = daily_deficit > 0
|
||||
|
||||
# 3. Protein Needs (Based on Lean Body Mass and age/deficit status)
|
||||
lean_mass_kg = weight_kg * (1 - (body_fat_percent / 100))
|
||||
|
||||
protein_multiplier = 1.8 # default fallback
|
||||
for (min_age, max_age), values in PROTEIN_GUIDELINES.items():
|
||||
if min_age <= age < max_age:
|
||||
protein_multiplier = values['deficit'] if is_deficit else values['maintenance']
|
||||
break
|
||||
|
||||
daily_protein_grams = lean_mass_kg * protein_multiplier
|
||||
protein_calories = daily_protein_grams * 4
|
||||
|
||||
# 4. Remaining Macros (Fats and Carbs)
|
||||
FAT_PERCENT_OF_TOTAL_CALORIES = 0.28 # Standard 25-30% fat allocation
|
||||
|
||||
fat_calories = target_calories * FAT_PERCENT_OF_TOTAL_CALORIES
|
||||
fat_grams = fat_calories / 9
|
||||
|
||||
carb_calories = target_calories - protein_calories - fat_calories
|
||||
carb_grams = carb_calories / 4
|
||||
|
||||
if carb_calories < 0:
|
||||
carb_calories = 0
|
||||
carb_grams = 0
|
||||
|
||||
return {
|
||||
"tdee_maintenance": int(round(maintenance_calories)),
|
||||
"daily_deficit": daily_deficit,
|
||||
"target_calories": int(round(target_calories)),
|
||||
"macros": {
|
||||
"protein_g": int(round(daily_protein_grams)),
|
||||
"fats_g": int(round(fat_grams)),
|
||||
"carbs_g": int(round(carb_grams))
|
||||
},
|
||||
"caloric_breakdown": {
|
||||
"protein_kcal": int(round(protein_calories)),
|
||||
"fats_kcal": int(round(fat_calories)),
|
||||
"carbs_kcal": int(round(carb_calories))
|
||||
}
|
||||
}
|
||||
|
||||
# --- EXECUTION EXAMPLE ---
|
||||
|
||||
# 1. Run Analysis on the CSV
|
||||
# Replace with your actual file path
|
||||
csv_result = analyze_pnoe_data('/home/oluwasanmi/Documents/Work/MKD/report_generation/data/Pnoe_20250729_1550-Moran_Keirstyn.csv')
|
||||
|
||||
# 2. Inputs for the Calculation (These would come from your UI/Form)
|
||||
user_weight = 85.0 # kg
|
||||
user_height = 180.0 # cm
|
||||
user_age = 35
|
||||
user_sex = 'male'
|
||||
user_body_fat = 20.0 # %
|
||||
user_activity = 'Moderate' # From the PDF list
|
||||
user_goal_loss = 0.5 # kg per week
|
||||
|
||||
# 3. Assess Health
|
||||
health_assessment = assess_metabolic_health(
|
||||
measured_rmr=csv_result['measured_rmr'],
|
||||
weight_kg=user_weight,
|
||||
height_cm=user_height,
|
||||
age=user_age,
|
||||
sex=user_sex
|
||||
)
|
||||
|
||||
# 4. Get Nutrition Plan
|
||||
nutrition_plan = generate_nutrition_plan(
|
||||
measured_rmr=csv_result['measured_rmr'],
|
||||
weight_kg=user_weight,
|
||||
body_fat_percent=user_body_fat,
|
||||
age=user_age,
|
||||
activity_level=user_activity,
|
||||
weekly_weight_loss_goal_kg=user_goal_loss
|
||||
)
|
||||
|
||||
# --- OUTPUT ---
|
||||
print("--- METABOLIC REPORT ---")
|
||||
print(f"Measured RMR: {csv_result['measured_rmr']} kcal/day")
|
||||
print(f"Predicted RMR: {health_assessment['predicted_rmr_mifflin']} kcal/day")
|
||||
print(f"Metabolism Status: {health_assessment['metabolism_type']} ({health_assessment['variance_percent']}%)")
|
||||
print(f"Fuel Source: {csv_result['fuel_source']['fat_percent']}% Fat, {csv_result['fuel_source']['carb_percent']}% Carbs")
|
||||
print("\n--- NUTRITION PLAN ---")
|
||||
print(f"Goal: Lose {user_goal_loss} kg/week")
|
||||
print(f"Daily Calorie Target: {nutrition_plan['target_calories']} kcal (Deficit: {nutrition_plan['daily_deficit']})")
|
||||
print("\nDaily Macros:")
|
||||
print(f"Protein: {nutrition_plan['macros']['protein_g']}g")
|
||||
print(f"Fats: {nutrition_plan['macros']['fats_g']}g")
|
||||
print(f"Carbs: {nutrition_plan['macros']['carbs_g']}g")
|
||||
@@ -0,0 +1,266 @@
|
||||
"""
|
||||
Test script for Page 6 - Meal Plan Calculations
|
||||
Using Keirstyn Moran's actual data
|
||||
|
||||
Expected values from PDF (Page 6):
|
||||
Row 1 (Caloric Deficit - 7 days same):
|
||||
- Calories: 1725 kCals
|
||||
- Protein: 120g (28%)
|
||||
- Carbs: 155g (36%)
|
||||
- Fat: 69g (36%)
|
||||
- Fiber: 25g
|
||||
|
||||
Row 2 (Caloric Deficit with Refeed - 5 weekdays low, 2 weekend high):
|
||||
Weekdays (5 days):
|
||||
- Calories: 1615 kCals
|
||||
- Protein: 120g
|
||||
- Carbs: 142g
|
||||
- Fat: 63g
|
||||
- Fiber: 24g
|
||||
|
||||
Weekends (2 days):
|
||||
- Calories: 2000 kCals
|
||||
- Protein: 120g
|
||||
- Carbs: 190g
|
||||
- Fat: 84g
|
||||
- Fiber: 30g
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, '/Users/macbook/bio-performx')
|
||||
|
||||
from app.services.context_generator import ContextGenerator
|
||||
|
||||
# Keirstyn Moran's patient data from PDF
|
||||
PATIENT_DATA = {
|
||||
"name": "Keirstyn Moran",
|
||||
"first_name": "Keirstyn",
|
||||
"last_name": "Moran",
|
||||
"age": 34,
|
||||
"height": "5'4\"", # 162.56 cm
|
||||
"weight": 55.79, # 123 lbs = 55.79 kg
|
||||
"gender": "female",
|
||||
"fat_percentage": 20.0, # Estimated
|
||||
"activity_level": "moderate",
|
||||
}
|
||||
|
||||
# RMR metrics from Page 5 (using expected PDF values)
|
||||
RMR_METRICS_EXPECTED = {
|
||||
"total_calories": 1725,
|
||||
"resting_calories": 1386,
|
||||
"neat_calories": 762,
|
||||
"weight_loss_calories": 423,
|
||||
}
|
||||
|
||||
def main():
|
||||
print("=" * 80)
|
||||
print("PAGE 6 - MEAL PLAN CALCULATION TEST")
|
||||
print("=" * 80)
|
||||
print(f"\nPatient: {PATIENT_DATA['name']}")
|
||||
print(f"Weight: {PATIENT_DATA['weight']}kg ({PATIENT_DATA['weight'] * 2.20462:.1f}lbs)")
|
||||
print(f"Body Fat: {PATIENT_DATA['fat_percentage']}%")
|
||||
|
||||
# Create context generator
|
||||
gen = ContextGenerator()
|
||||
|
||||
# Set patient info manually
|
||||
gen.patient_info = PATIENT_DATA.copy()
|
||||
|
||||
# Calculate fat mass and lean mass
|
||||
weight_kg = PATIENT_DATA["weight"]
|
||||
fat_pct = PATIENT_DATA["fat_percentage"]
|
||||
lean_mass_kg = weight_kg * (1 - fat_pct / 100)
|
||||
lean_mass_lbs = lean_mass_kg * 2.20462
|
||||
|
||||
gen.patient_info["fat_mass_lbs"] = weight_kg * fat_pct / 100 * 2.20462
|
||||
gen.patient_info["lean_mass_lbs"] = lean_mass_lbs
|
||||
|
||||
print(f"Lean Mass: {lean_mass_lbs:.2f} lbs ({lean_mass_kg:.2f} kg)")
|
||||
print(f"Fat Mass: {gen.patient_info['fat_mass_lbs']:.2f} lbs")
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("CALCULATING MEAL PLAN (using our formula)")
|
||||
print("=" * 80)
|
||||
print(f"\nTotal Daily Calories (from Page 5): {RMR_METRICS_EXPECTED['total_calories']} kcal")
|
||||
|
||||
# Calculate meal plan using our formula
|
||||
try:
|
||||
meal_metrics = gen.calculate_meal_plan_breakdown(RMR_METRICS_EXPECTED)
|
||||
|
||||
print("\n--- Protein Calculation (Bio-PerformX Formula) ---")
|
||||
print(f"Formula: Total Body Weight (kg) × 2.15 g/kg")
|
||||
print(f" = {weight_kg:.2f} × 2.15")
|
||||
protein_grams = weight_kg * 2.15
|
||||
print(f" = {protein_grams:.0f}g protein")
|
||||
protein_calories = protein_grams * 4
|
||||
print(f" = {protein_calories:.0f} kcal from protein")
|
||||
|
||||
print("\n--- Carbs and Fats (50/50 split of remaining calories) ---")
|
||||
remaining = RMR_METRICS_EXPECTED['total_calories'] - protein_calories
|
||||
print(f"Remaining calories: {RMR_METRICS_EXPECTED['total_calories']} - {protein_calories:.0f} = {remaining:.0f} kcal")
|
||||
print(f"Carbs (50%): {remaining * 0.5:.0f} kcal ÷ 4 = {remaining * 0.5 / 4:.0f}g")
|
||||
print(f"Fats (50%): {remaining * 0.5:.0f} kcal ÷ 9 = {remaining * 0.5 / 9:.0f}g")
|
||||
|
||||
print("\n--- Fiber Calculation ---")
|
||||
print(f"Formula: 15g per 1000 calories")
|
||||
print(f" = {RMR_METRICS_EXPECTED['total_calories']} ÷ 1000 × 15")
|
||||
print(f" = {RMR_METRICS_EXPECTED['total_calories'] / 1000 * 15:.0f}g")
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("ROW 1: CALORIC DEFICIT (7 days same)")
|
||||
print("=" * 80)
|
||||
print(f"Calories: {meal_metrics['deficit_calories']} kcal")
|
||||
print(f"Protein: {meal_metrics['deficit_protein']}g ({meal_metrics['protein_percentage']}%)")
|
||||
print(f"Carbs: {meal_metrics['deficit_carbs']}g ({meal_metrics['carbs_percentage']}%)")
|
||||
print(f"Fat: {meal_metrics['deficit_fat']}g ({meal_metrics['fats_percentage']}%)")
|
||||
print(f"Fiber: {meal_metrics['deficit_fiber']}g")
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("ROW 2: CALORIC DEFICIT WITH REFEED (5 weekdays + 2 weekends)")
|
||||
print("=" * 80)
|
||||
|
||||
print("\nWeekdays (5 days):")
|
||||
print(f"Calories: {meal_metrics['refeed_weekday_calories']} kcal")
|
||||
print(f"Protein: {meal_metrics['refeed_weekday_protein']}g")
|
||||
print(f"Carbs: {meal_metrics['refeed_weekday_carbs']}g")
|
||||
print(f"Fat: {meal_metrics['refeed_weekday_fat']}g")
|
||||
print(f"Fiber: {meal_metrics['refeed_weekday_fiber']}g")
|
||||
|
||||
print("\nWeekends (2 days):")
|
||||
print(f"Calories: {meal_metrics['refeed_weekend_calories']} kcal")
|
||||
print(f"Protein: {meal_metrics['refeed_weekend_protein']}g")
|
||||
print(f"Carbs: {meal_metrics['refeed_weekend_carbs']}g")
|
||||
print(f"Fat: {meal_metrics['refeed_weekend_fat']}g")
|
||||
print(f"Fiber: {meal_metrics['refeed_weekend_fiber']}g")
|
||||
|
||||
print("\n--- Weekly Total Verification ---")
|
||||
weekly_total_row1 = meal_metrics['deficit_calories'] * 7
|
||||
weekly_total_row2 = (meal_metrics['refeed_weekday_calories'] * 5) + (meal_metrics['refeed_weekend_calories'] * 2)
|
||||
print(f"Row 1 Weekly Total: {meal_metrics['deficit_calories']} × 7 = {weekly_total_row1} kcal")
|
||||
print(f"Row 2 Weekly Total: ({meal_metrics['refeed_weekday_calories']} × 5) + ({meal_metrics['refeed_weekend_calories']} × 2) = {weekly_total_row2} kcal")
|
||||
print(f"Difference: {abs(weekly_total_row1 - weekly_total_row2)} kcal (should be ~0)")
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("EXPECTED VALUES (From PDF Page 6)")
|
||||
print("=" * 80)
|
||||
|
||||
print("\nRow 1 (Deficit - 7 days):")
|
||||
print("Calories: 1725 kcal")
|
||||
print("Protein: 120g (28%)")
|
||||
print("Carbs: 155g (36%)")
|
||||
print("Fat: 69g (36%)")
|
||||
print("Fiber: 25g")
|
||||
|
||||
print("\nRow 2 Weekdays:")
|
||||
print("Calories: 1615 kcal")
|
||||
print("Protein: 120g")
|
||||
print("Carbs: 142g")
|
||||
print("Fat: 63g")
|
||||
print("Fiber: 24g")
|
||||
|
||||
print("\nRow 2 Weekends:")
|
||||
print("Calories: 2000 kcal")
|
||||
print("Protein: 120g")
|
||||
print("Carbs: 190g")
|
||||
print("Fat: 84g")
|
||||
print("Fiber: 30g")
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("COMPARISON")
|
||||
print("=" * 80)
|
||||
|
||||
expected_row1 = {
|
||||
"calories": 1725,
|
||||
"protein": 120,
|
||||
"carbs": 155,
|
||||
"fat": 69,
|
||||
"fiber": 25
|
||||
}
|
||||
|
||||
expected_weekday = {
|
||||
"calories": 1615,
|
||||
"protein": 120,
|
||||
"carbs": 142,
|
||||
"fat": 63,
|
||||
"fiber": 24
|
||||
}
|
||||
|
||||
expected_weekend = {
|
||||
"calories": 2000,
|
||||
"protein": 120,
|
||||
"carbs": 190,
|
||||
"fat": 84,
|
||||
"fiber": 30
|
||||
}
|
||||
|
||||
def compare(label, expected_val, actual_val, unit=""):
|
||||
diff = actual_val - expected_val
|
||||
pct_diff = (diff / expected_val * 100) if expected_val != 0 else 0
|
||||
status = "✓" if abs(pct_diff) < 5 else "✗"
|
||||
print(f"{status} {label:25} Expected: {expected_val:5}{unit} Actual: {actual_val:5}{unit} Diff: {diff:+5.0f} ({pct_diff:+.1f}%)")
|
||||
|
||||
print("\nRow 1 (Deficit - 7 days):")
|
||||
compare("Calories", expected_row1['calories'], meal_metrics['deficit_calories'], " kcal")
|
||||
compare("Protein", expected_row1['protein'], meal_metrics['deficit_protein'], "g")
|
||||
compare("Carbs", expected_row1['carbs'], meal_metrics['deficit_carbs'], "g")
|
||||
compare("Fat", expected_row1['fat'], meal_metrics['deficit_fat'], "g")
|
||||
compare("Fiber", expected_row1['fiber'], meal_metrics['deficit_fiber'], "g")
|
||||
|
||||
print("\nRow 2 Weekdays:")
|
||||
compare("Calories", expected_weekday['calories'], meal_metrics['refeed_weekday_calories'], " kcal")
|
||||
compare("Protein", expected_weekday['protein'], meal_metrics['refeed_weekday_protein'], "g")
|
||||
compare("Carbs", expected_weekday['carbs'], meal_metrics['refeed_weekday_carbs'], "g")
|
||||
compare("Fat", expected_weekday['fat'], meal_metrics['refeed_weekday_fat'], "g")
|
||||
compare("Fiber", expected_weekday['fiber'], meal_metrics['refeed_weekday_fiber'], "g")
|
||||
|
||||
print("\nRow 2 Weekends:")
|
||||
compare("Calories", expected_weekend['calories'], meal_metrics['refeed_weekend_calories'], " kcal")
|
||||
compare("Protein", expected_weekend['protein'], meal_metrics['refeed_weekend_protein'], "g")
|
||||
compare("Carbs", expected_weekend['carbs'], meal_metrics['refeed_weekend_carbs'], "g")
|
||||
compare("Fat", expected_weekend['fat'], meal_metrics['refeed_weekend_fat'], "g")
|
||||
compare("Fiber", expected_weekend['fiber'], meal_metrics['refeed_weekend_fiber'], "g")
|
||||
|
||||
# Overall assessment
|
||||
row1_match = all([
|
||||
abs(meal_metrics['deficit_calories'] - expected_row1['calories']) <= 5,
|
||||
abs(meal_metrics['deficit_protein'] - expected_row1['protein']) <= 5,
|
||||
abs(meal_metrics['deficit_carbs'] - expected_row1['carbs']) <= 5,
|
||||
abs(meal_metrics['deficit_fat'] - expected_row1['fat']) <= 5,
|
||||
])
|
||||
|
||||
weekday_match = all([
|
||||
abs(meal_metrics['refeed_weekday_calories'] - expected_weekday['calories']) <= 10,
|
||||
abs(meal_metrics['refeed_weekday_protein'] - expected_weekday['protein']) <= 5,
|
||||
abs(meal_metrics['refeed_weekday_carbs'] - expected_weekday['carbs']) <= 5,
|
||||
abs(meal_metrics['refeed_weekday_fat'] - expected_weekday['fat']) <= 5,
|
||||
])
|
||||
|
||||
weekend_match = all([
|
||||
abs(meal_metrics['refeed_weekend_calories'] - expected_weekend['calories']) <= 10,
|
||||
abs(meal_metrics['refeed_weekend_protein'] - expected_weekend['protein']) <= 5,
|
||||
abs(meal_metrics['refeed_weekend_carbs'] - expected_weekend['carbs']) <= 10,
|
||||
abs(meal_metrics['refeed_weekend_fat'] - expected_weekend['fat']) <= 5,
|
||||
])
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
if row1_match and weekday_match and weekend_match:
|
||||
print("✓ SUCCESS: Our formula produces values matching the PDF!")
|
||||
else:
|
||||
print("✗ WARNING: Significant differences found. Check:")
|
||||
if not row1_match:
|
||||
print(" - Row 1 calculations (daily deficit)")
|
||||
if not weekday_match:
|
||||
print(" - Weekday calculations (10% reduction)")
|
||||
if not weekend_match:
|
||||
print(" - Weekend calculations (maintaining weekly total)")
|
||||
print("\nNote: Protein formula is Bio-PerformX specific: Lean Mass (lbs) × 2.2")
|
||||
print("=" * 80)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n✗ Error calculating metrics: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,15 @@
|
||||
[project]
|
||||
name = "report-generation"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"fastapi[all]>=0.121.3",
|
||||
"matplotlib>=3.10.7",
|
||||
"openpyxl>=3.1.5",
|
||||
"pandas>=2.3.3",
|
||||
"playwright>=1.56.0",
|
||||
"requests>=2.32.5",
|
||||
"seaborn>=0.13.2",
|
||||
]
|
||||
Reference in New Issue
Block a user