added minimal report
This commit is contained in:
@@ -111,6 +111,7 @@ async def upload_files(
|
||||
focus: str = Form(default="Endurance"),
|
||||
session_id: str = Form(default="default"),
|
||||
next_testing_date: str = Form(...),
|
||||
report_type: str = Form(default="full"),
|
||||
spirometry_pdf: UploadFile = File(...),
|
||||
pnoe_csv: UploadFile = File(...),
|
||||
oxygenation_csv: UploadFile = File(None),
|
||||
@@ -199,12 +200,14 @@ async def upload_files(
|
||||
pnoe_csv_path=str(pnoe_path),
|
||||
patient_info=patient_info,
|
||||
oxygenation_csv_path=oxygenation_csv_path,
|
||||
report_type=report_type,
|
||||
)
|
||||
|
||||
# Store in session
|
||||
request.session["patient_info"] = patient_info
|
||||
request.session["temp_dir"] = str(session_temp_dir)
|
||||
request.session["report_path"] = result["report_path"]
|
||||
request.session["report_type"] = report_type
|
||||
request.session["graphs_generated"] = result["graphs_generated"]
|
||||
request.session["analysis_data"] = result["analysis_data"]
|
||||
|
||||
@@ -408,6 +411,8 @@ async def edit_metrics(request: Request):
|
||||
raise ValueError("Could not find all required uploaded files")
|
||||
|
||||
# Regenerate report with overrides
|
||||
# Get report_type from session or default to "full"
|
||||
report_type = request.session.get("report_type", "full")
|
||||
oxygenation_csv_path = str(oxygenation_path) if oxygenation_path else None
|
||||
result = await report_service.generate_report(
|
||||
spirometry_pdf_path=str(spirometry_path),
|
||||
@@ -417,6 +422,7 @@ async def edit_metrics(request: Request):
|
||||
if (metric_overrides["pnoe"] or metric_overrides["spirometry"])
|
||||
else None,
|
||||
oxygenation_csv_path=oxygenation_csv_path,
|
||||
report_type=report_type,
|
||||
)
|
||||
|
||||
# Update session with new report
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<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">Glossary</h1>
|
||||
|
||||
<!-- Body Fat Percentage -->
|
||||
<div class="mb-6">
|
||||
<h2 class="text-lg font-bold text-black mb-2">Body Fat Percentage:</h2>
|
||||
<p class="text-sm text-black leading-relaxed">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-6">
|
||||
<h2 class="text-lg font-bold text-black mb-2">Metabolic Rate:</h2>
|
||||
<p class="text-sm text-black leading-relaxed">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-6">
|
||||
<h2 class="text-lg font-bold text-black mb-2">Fuel Source:</h2>
|
||||
<p class="text-sm text-black leading-relaxed mb-2">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-sm text-black leading-relaxed">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-6">
|
||||
<h2 class="text-lg font-bold text-black mb-2">NEAT (Non-Exercise Activity Thermogenesis)</h2>
|
||||
<p class="text-sm text-black leading-relaxed">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>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,470 @@
|
||||
<div class="w-full page bg-white">
|
||||
<!-- Main Content -->
|
||||
<div class="p-8">
|
||||
<!-- Body Fat Percent Master Chart Section -->
|
||||
<div class="mb-8">
|
||||
<h1 class="text-2xl font-bold mb-4 text-center">
|
||||
Body Fat Percent Master Chart
|
||||
</h1>
|
||||
<div class="w-full max-w-5xl mx-auto">
|
||||
<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>
|
||||
|
||||
<!-- 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-blue-300">
|
||||
<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">
|
||||
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">
|
||||
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">
|
||||
40-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">
|
||||
42-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">
|
||||
45-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">
|
||||
78-83bpm
|
||||
</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">
|
||||
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">
|
||||
48-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">
|
||||
84bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
78-83bpm
|
||||
</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">
|
||||
52-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-blue-300">
|
||||
<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">
|
||||
82bpm +
|
||||
</td>
|
||||
<td class="border border-gray-300 p-1 text-center">
|
||||
74-81bpm
|
||||
</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">
|
||||
40-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">
|
||||
44-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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<div class="bg-white w-full page m-0 px-10">
|
||||
<div class="px-16 py-10">
|
||||
<!-- Table of Contents Header -->
|
||||
<div class="mb-8">
|
||||
<h1
|
||||
class="text-5xl font-bold text-black mb-6 tracking-wide border-b-4 border-blue-500 pb-2 text-center"
|
||||
>
|
||||
TABLE OF CONTENTS
|
||||
</h1>
|
||||
<div class="w-full h-1 bg-cyan-400"></div>
|
||||
</div>
|
||||
|
||||
<!-- Table of Contents Items -->
|
||||
<div class="flex flex-col justify-between space-y-6 py-6">
|
||||
<!-- Nutrition Guidelines -->
|
||||
<div class="flex items-start bg-gray-200 h-24">
|
||||
<div
|
||||
class="bg-black text-white text-2xl font-bold w-16 h-full flex items-center justify-center mr-8 flex-shrink-0"
|
||||
>
|
||||
3
|
||||
</div>
|
||||
<div class="flex flex-col flex-1 py-1 justify-center h-full">
|
||||
<h2 class="text-2xl font-semibold text-black">
|
||||
Nutrition Guidelines
|
||||
</h2>
|
||||
<p class="text-gray-600 text-base">
|
||||
Ultrasound & Body Composition
|
||||
</p>
|
||||
<p class="text-gray-600 text-base">
|
||||
Resting Metabolic Rate Assessment
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nutrition Recommendations -->
|
||||
<div class="flex items-start bg-gray-200 h-24">
|
||||
<div
|
||||
class="bg-black text-white text-2xl font-bold w-16 h-full flex items-center justify-center mr-8 flex-shrink-0"
|
||||
>
|
||||
4
|
||||
</div>
|
||||
<div class="flex flex-col py-1 flex-1 justify-center h-full">
|
||||
<h2 class="text-2xl font-semibold text-black mb-3">
|
||||
Nutrition Recommendations
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Next Steps -->
|
||||
<div class="flex items-start bg-gray-200 h-24">
|
||||
<div
|
||||
class="bg-black text-white text-2xl font-bold w-16 h-full flex items-center justify-center mr-8 flex-shrink-0"
|
||||
>
|
||||
5
|
||||
</div>
|
||||
<div class="flex flex-col h-full justify-center flex-1">
|
||||
<h2 class="text-2xl font-semibold text-black">
|
||||
Next Steps
|
||||
</h2>
|
||||
<div class="space-y-2">
|
||||
<!-- No sub-items -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Glossary -->
|
||||
<div class="flex items-start bg-gray-200 h-24">
|
||||
<div
|
||||
class="bg-black text-white text-2xl font-bold w-16 h-full flex items-center justify-center mr-8 flex-shrink-0"
|
||||
>
|
||||
6
|
||||
</div>
|
||||
<div class="flex flex-col h-full justify-center flex-1">
|
||||
<h2 class="text-2xl font-semibold text-black">
|
||||
Glossary
|
||||
</h2>
|
||||
<div class="space-y-2">
|
||||
<!-- No sub-items -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<div class="w-full bg-white">
|
||||
<!-- Header Section -->
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="px-8 py-2">
|
||||
<!-- Page Title -->
|
||||
<h1 class="text-3xl font-bold text-black mb-3">Nutrition Guidelines</h1>
|
||||
|
||||
<!-- Section Title -->
|
||||
<h2 class="text-xl font-bold text-black mb-2">
|
||||
Resting Metabolic Rate Assessment
|
||||
</h2>
|
||||
<p class="text-gray-700 text-sm mb-4">
|
||||
The resting metabolic rate assessment determines the number of
|
||||
calories that you burn at rest, and metabolic health. It is also an
|
||||
indicator of overall health and well-being.
|
||||
</p>
|
||||
|
||||
<!-- Slow vs Fast Metabolism Section -->
|
||||
<div class="mb-6">
|
||||
<div class="flex justify-center">
|
||||
<img
|
||||
src="data:image/png;base64,{{ metabolism_chart }}"
|
||||
alt="Slow vs Fast Metabolism Chart"
|
||||
class="max-w-full h-auto max-h-40"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fuel Source Section -->
|
||||
<div class="mb-6">
|
||||
<div class="flex justify-center">
|
||||
<img
|
||||
src="data:image/png;base64,{{ fuel_source_chart }}"
|
||||
alt="Fuel Source Chart"
|
||||
class="max-w-full h-auto max-h-40"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Caloric Intake Section -->
|
||||
<div class="px-6 mb-6">
|
||||
<h3 class="text-2xl font-bold text-black mb-4 text-center">
|
||||
Caloric Intake
|
||||
</h3>
|
||||
|
||||
<!-- Calculation Formula -->
|
||||
<div class="flex items-center justify-center space-x-4 text-center">
|
||||
<!-- Resting Metabolic -->
|
||||
<div class="bg-gray-200 p-3 rounded-lg">
|
||||
<div class="text-lg font-bold text-black">
|
||||
{{ resting_calories }}kCals
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 mt-1">
|
||||
<div>Resting</div>
|
||||
<div>Metabolic</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Plus sign -->
|
||||
<div class="text-2xl font-bold text-black">+</div>
|
||||
|
||||
<!-- NEAT -->
|
||||
<div class="bg-gray-200 p-3 rounded-lg">
|
||||
<div class="text-lg font-bold text-black">
|
||||
{{ neat_calories }}kCals
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 mt-1">NEAT</div>
|
||||
</div>
|
||||
|
||||
<!-- Minus sign -->
|
||||
<div class="text-2xl font-bold text-black">-</div>
|
||||
|
||||
<!-- Weight Loss -->
|
||||
<div class="bg-gray-200 p-3 rounded-lg">
|
||||
<div class="text-lg font-bold text-black">
|
||||
{{ weight_loss_calories }}kCals
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 mt-1">
|
||||
<div>to lose {{ weight_loss_rate }}lbs</div>
|
||||
<div>per week</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Equals sign -->
|
||||
<div class="text-2xl font-bold text-black">=</div>
|
||||
|
||||
<!-- Total -->
|
||||
<div class="bg-gray-200 p-3 rounded-lg">
|
||||
<div class="text-lg font-bold text-black">
|
||||
~{{ total_calories }}kCals
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resting Heart Rate Table Section -->
|
||||
{% if rhr_table %}
|
||||
<div class="mb-4">
|
||||
<h2 class="text-xl font-bold text-black mb-4 text-center">
|
||||
Resting Heart Rate
|
||||
</h2>
|
||||
<div class="flex justify-center">
|
||||
<img
|
||||
src="data:image/png;base64, {{ rhr_table }}"
|
||||
alt="Resting Heart Rate Table"
|
||||
class="table-image"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1112,9 +1112,17 @@ class ContextGenerator:
|
||||
graphs: Dict[str, str],
|
||||
metric_overrides: Optional[Dict] = None,
|
||||
graph_generator: Optional[Any] = None,
|
||||
report_type: str = "full",
|
||||
) -> Dict[str, Dict]:
|
||||
"""Main method to generate all page contexts
|
||||
|
||||
Args:
|
||||
patient_name: Patient name
|
||||
graphs: Dictionary of graph data
|
||||
metric_overrides: Optional metric overrides
|
||||
graph_generator: Optional graph generator instance
|
||||
report_type: Type of report ("full" or "minimal")
|
||||
|
||||
Returns:
|
||||
Dictionary with keys 'page_1', 'page_2', etc., each containing context data for that page
|
||||
"""
|
||||
@@ -1133,7 +1141,17 @@ class ContextGenerator:
|
||||
|
||||
contexts = {}
|
||||
|
||||
# Define which pages to generate based on report type
|
||||
if report_type == "minimal":
|
||||
# Minimal report only needs pages: 1, 2, 4, 5, 6, 16, 17, 19, 20
|
||||
# But we'll generate contexts for all needed pages and combine 19+20
|
||||
pages_to_generate = [1, 2, 4, 5, 6, 16, 17, 19, 20]
|
||||
else:
|
||||
# Full report needs all pages 1-20
|
||||
pages_to_generate = list(range(1, 21))
|
||||
|
||||
# Page 1
|
||||
if 1 in pages_to_generate:
|
||||
contexts["page_1"] = {
|
||||
"name": self.patient_info["name"],
|
||||
"surname": self.patient_info["last_name"],
|
||||
@@ -1141,12 +1159,14 @@ class ContextGenerator:
|
||||
}
|
||||
|
||||
# Page 2
|
||||
if 2 in pages_to_generate:
|
||||
contexts["page_2"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"test_date": datetime.now().strftime("%B %d, %Y"),
|
||||
}
|
||||
|
||||
# Pages 3, 6 (pages 4 and 5 are handled separately)
|
||||
if report_type == "full":
|
||||
for i in [0, 3]: # Skip indices 1 and 2 which are pages 4 and 5
|
||||
contexts[f"page_{i + 3}"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
@@ -1154,6 +1174,7 @@ class ContextGenerator:
|
||||
}
|
||||
|
||||
# Page 4 - Nutrition Guidelines with Body Composition
|
||||
if 4 in pages_to_generate:
|
||||
contexts["page_4"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": 4,
|
||||
@@ -1166,6 +1187,7 @@ class ContextGenerator:
|
||||
}
|
||||
|
||||
# Page 5 - Resting Metabolic Rate Assessment
|
||||
if 5 in pages_to_generate:
|
||||
contexts["page_5"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": 5,
|
||||
@@ -1178,6 +1200,88 @@ class ContextGenerator:
|
||||
"total_calories": rmr_metrics.get("total_calories", 1375),
|
||||
}
|
||||
|
||||
# For minimal reports, also generate resting heart rate table for page_5
|
||||
if report_type == "minimal" and graph_generator:
|
||||
resting_hr_metrics = self._calculate_resting_heart_rate_metrics()
|
||||
rhr_table_info = self._calculate_rhr_table_data(
|
||||
self.patient_info["age"], self.patient_info["gender"]
|
||||
)
|
||||
|
||||
# Get resting heart rate value and determine category
|
||||
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_5"]["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 6 - Meal Plan (needed for both full and minimal)
|
||||
if 6 in pages_to_generate:
|
||||
contexts["page_6"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": 6,
|
||||
"deficit_calories": rmr_metrics.get("total_calories", 1600),
|
||||
"deficit_protein": f"{int(rmr_metrics.get('total_calories', 1600) * 0.22 / 4)}g Protein",
|
||||
"deficit_carbs": f"{int(rmr_metrics.get('total_calories', 1600) * 0.39 / 4)}g Carbs",
|
||||
"deficit_fat": f"{int(rmr_metrics.get('total_calories', 1600) * 0.39 / 9)}g Fat",
|
||||
"deficit_fiber": "24g Fibre",
|
||||
"refeed_weekday_calories": int(rmr_metrics.get("total_calories", 1600) * 0.85),
|
||||
"refeed_weekday_protein": f"{int(rmr_metrics.get('total_calories', 1600) * 0.85 * 0.22 / 4)}g Protein",
|
||||
"refeed_weekday_carbs": f"{int(rmr_metrics.get('total_calories', 1600) * 0.85 * 0.39 / 4)}g Carbs",
|
||||
"refeed_weekday_fat": f"{int(rmr_metrics.get('total_calories', 1600) * 0.85 * 0.39 / 9)}g Fat",
|
||||
"refeed_weekday_fiber": "20g Fibre",
|
||||
"refeed_weekend_calories": int(rmr_metrics.get("total_calories", 1600) * 1.375),
|
||||
"refeed_weekend_protein": f"{int(rmr_metrics.get('total_calories', 1600) * 1.375 * 0.22 / 4)}g Protein",
|
||||
"refeed_weekend_carbs": f"{int(rmr_metrics.get('total_calories', 1600) * 1.375 * 0.39 / 4)}g Carbs",
|
||||
"refeed_weekend_fat": f"{int(rmr_metrics.get('total_calories', 1600) * 1.375 * 0.39 / 9)}g Fat",
|
||||
"refeed_weekend_fiber": "33g Fibre",
|
||||
"protein_percentage": "22%",
|
||||
"carbs_percentage": "39%",
|
||||
"fats_percentage": "39%",
|
||||
}
|
||||
|
||||
# Only generate pages 7-15 and 18 for full reports
|
||||
if report_type == "full":
|
||||
# Calculate FEV1 percentage for page 7
|
||||
fev1_percentage = 0
|
||||
if spirometry_metrics.get("fvc_best"):
|
||||
@@ -1423,7 +1527,25 @@ class ContextGenerator:
|
||||
"next_testing_date", "Contact us for scheduling"
|
||||
)
|
||||
|
||||
# Page 19 - Glossary with Body Fat Percentage Master Chart (previously page 18)
|
||||
# Page 16 - Next Steps (needed for both full and minimal)
|
||||
if 16 in pages_to_generate:
|
||||
contexts["page_16"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": 16,
|
||||
"next_testing_date": self.patient_info.get(
|
||||
"next_testing_date", "Contact us for scheduling"
|
||||
),
|
||||
}
|
||||
|
||||
# Page 17 - Glossary (needed for both full and minimal, but minimal uses different template)
|
||||
if 17 in pages_to_generate:
|
||||
contexts["page_17"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": 17,
|
||||
}
|
||||
|
||||
# Page 19 - Glossary with Body Fat Percentage Master Chart
|
||||
if 19 in pages_to_generate:
|
||||
contexts["page_19"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": 19,
|
||||
@@ -1432,10 +1554,20 @@ class ContextGenerator:
|
||||
),
|
||||
}
|
||||
|
||||
# Page 20 (previously page 19)
|
||||
# Page 20 - Resting Heart Rate Table
|
||||
if 20 in pages_to_generate:
|
||||
contexts["page_20"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": 20,
|
||||
}
|
||||
|
||||
# For minimal reports, create combined context for page_19_20_minimal
|
||||
if report_type == "minimal" and 19 in pages_to_generate and 20 in pages_to_generate:
|
||||
contexts["page_19_20_minimal"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"body_fat_percentage_chart": graphs.get(
|
||||
"body_fat_percentage_master_chart", ""
|
||||
),
|
||||
}
|
||||
|
||||
return contexts
|
||||
|
||||
@@ -151,7 +151,7 @@ class ReportGeneratorService:
|
||||
}
|
||||
|
||||
def generate_html(
|
||||
self, patient_info: Dict[str, Any], contexts: Dict[str, Dict[str, Any]]
|
||||
self, patient_info: Dict[str, Any], contexts: Dict[str, Dict[str, Any]], report_type: str = "full"
|
||||
) -> str:
|
||||
"""
|
||||
Generate HTML content for the report.
|
||||
@@ -160,6 +160,7 @@ class ReportGeneratorService:
|
||||
patient_info: Dictionary containing patient information
|
||||
(patient_name, age, height, weight, focus)
|
||||
contexts: Dictionary with keys 'page_1', 'page_2', etc., each containing context data
|
||||
report_type: Type of report to generate ("full" or "minimal")
|
||||
|
||||
Returns:
|
||||
Complete HTML document as string
|
||||
@@ -175,8 +176,28 @@ class ReportGeneratorService:
|
||||
"focus": patient_info.get("focus", "Endurance"),
|
||||
}
|
||||
|
||||
# Get total number of pages
|
||||
num_pages = len(contexts)
|
||||
# Define page mappings for full vs minimal reports
|
||||
if report_type == "minimal":
|
||||
# Minimal report: pages 1, 2, 4, 5, 6, 16, 17, 19, 20
|
||||
# Map to minimal report pages 1-8
|
||||
# Page mapping: (original_page_num, template_name, minimal_page_num)
|
||||
page_mapping = [
|
||||
(1, "page_1.html", 1),
|
||||
(2, "page_2_minimal.html", 2),
|
||||
(4, "page_4.html", 3),
|
||||
(5, "page_5_minimal.html", 4),
|
||||
(6, "page_6.html", 5),
|
||||
(16, "page_16.html", 6),
|
||||
(17, "page_17_minimal.html", 7),
|
||||
(19, "page_19_20_minimal.html", 8), # Combined page
|
||||
]
|
||||
else:
|
||||
# Full report: all pages 1-20
|
||||
page_mapping = [
|
||||
(i, f"page_{i}.html", i) for i in range(1, 21)
|
||||
]
|
||||
|
||||
num_pages = len(page_mapping)
|
||||
|
||||
# Footer context
|
||||
footer_context = [
|
||||
@@ -198,13 +219,20 @@ class ReportGeneratorService:
|
||||
for context in footer_context
|
||||
]
|
||||
|
||||
# Render pages - iterate through pages in order
|
||||
for i in range(1, num_pages + 1):
|
||||
page_key = f"page_{i}"
|
||||
# Render pages based on mapping
|
||||
for idx, (original_page_num, template_name, minimal_page_num) in enumerate(page_mapping):
|
||||
# For combined page_19_20_minimal, use the combined context
|
||||
if template_name == "page_19_20_minimal.html":
|
||||
page_key = "page_19_20_minimal"
|
||||
else:
|
||||
page_key = f"page_{original_page_num}"
|
||||
context = contexts.get(page_key, {})
|
||||
template = self.env.get_template(f"page_{i}.html").render(context)
|
||||
template = self.env.get_template(template_name).render(context)
|
||||
|
||||
if i > 2:
|
||||
# Pages 1 and 2 don't have headers/footers in full report
|
||||
# In minimal report, only page 1 doesn't have header/footer
|
||||
page_num_in_report = minimal_page_num if report_type == "minimal" else original_page_num
|
||||
if page_num_in_report > 2:
|
||||
full_html = f"""
|
||||
<div class="page flex flex-col justify-between">
|
||||
<div>
|
||||
@@ -214,7 +242,7 @@ class ReportGeneratorService:
|
||||
{template}
|
||||
</main>
|
||||
<div class="border-t text-center text-sm text-gray-600">
|
||||
{footer_html_list[i - 1]}
|
||||
{footer_html_list[idx]}
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
@@ -300,6 +328,7 @@ class ReportGeneratorService:
|
||||
output_filename: str = None,
|
||||
metric_overrides: Optional[Dict[str, Any]] = None,
|
||||
oxygenation_csv_path: Optional[str] = None,
|
||||
report_type: str = "full",
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate complete medical report from uploaded files.
|
||||
@@ -535,6 +564,7 @@ class ReportGeneratorService:
|
||||
graphs_dict,
|
||||
metric_overrides=metric_overrides,
|
||||
graph_generator=self.graph_generator,
|
||||
report_type=report_type,
|
||||
)
|
||||
|
||||
# Step 5: Calculate analysis metrics
|
||||
@@ -542,7 +572,7 @@ class ReportGeneratorService:
|
||||
analysis_data["graphs_count"] = len(graphs_generated)
|
||||
|
||||
# Step 6: Generate HTML
|
||||
html_content = self.generate_html(patient_info, contexts)
|
||||
html_content = self.generate_html(patient_info, contexts, report_type=report_type)
|
||||
|
||||
# Step 7: Generate PDF
|
||||
if output_filename is None:
|
||||
|
||||
@@ -146,6 +146,45 @@ Generator{% endblock %} {% block content %}
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>Report Type</label
|
||||
>
|
||||
<div class="mt-1 space-y-2">
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
name="report_type"
|
||||
id="report_type_full"
|
||||
value="full"
|
||||
checked
|
||||
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="report_type_full"
|
||||
class="ml-2 block text-sm text-gray-700"
|
||||
>
|
||||
Full Report
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
name="report_type"
|
||||
id="report_type_minimal"
|
||||
value="minimal"
|
||||
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="report_type_minimal"
|
||||
class="ml-2 block text-sm text-gray-700"
|
||||
>
|
||||
Minimal Report
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
|
||||
+22648
File diff suppressed because it is too large
Load Diff
@@ -3441,7 +3441,7 @@
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": ".venv",
|
||||
"display_name": "report-generation",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user