added minimal report
This commit is contained in:
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,60 +1141,156 @@ 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
|
||||
contexts["page_1"] = {
|
||||
"name": self.patient_info["name"],
|
||||
"surname": self.patient_info["last_name"],
|
||||
"date": datetime.now().strftime("%B %d, %Y"),
|
||||
}
|
||||
|
||||
# Page 2
|
||||
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)
|
||||
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"],
|
||||
"page_number": i + 3,
|
||||
if 1 in pages_to_generate:
|
||||
contexts["page_1"] = {
|
||||
"name": self.patient_info["name"],
|
||||
"surname": self.patient_info["last_name"],
|
||||
"date": datetime.now().strftime("%B %d, %Y"),
|
||||
}
|
||||
|
||||
# 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"],
|
||||
"page_number": i + 3,
|
||||
}
|
||||
|
||||
# Page 4 - Nutrition Guidelines with Body Composition
|
||||
contexts["page_4"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": 4,
|
||||
"fat_percentage": f"{self.patient_info['fat_percentage']:.1f}",
|
||||
"body_composition_chart": graphs.get("body_composition", ""),
|
||||
"body_fat_chart": graphs.get("body_fat_percent", ""), # Alias for template
|
||||
"body_fat_percent_chart": graphs.get(
|
||||
"body_fat_percent", ""
|
||||
), # Keep for consistency
|
||||
}
|
||||
if 4 in pages_to_generate:
|
||||
contexts["page_4"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": 4,
|
||||
"fat_percentage": f"{self.patient_info['fat_percentage']:.1f}",
|
||||
"body_composition_chart": graphs.get("body_composition", ""),
|
||||
"body_fat_chart": graphs.get("body_fat_percent", ""), # Alias for template
|
||||
"body_fat_percent_chart": graphs.get(
|
||||
"body_fat_percent", ""
|
||||
), # Keep for consistency
|
||||
}
|
||||
|
||||
# Page 5 - Resting Metabolic Rate Assessment
|
||||
contexts["page_5"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": 5,
|
||||
"metabolism_chart": graphs.get("metabolism_chart", ""),
|
||||
"fuel_source_chart": graphs.get("fuel_source_chart", ""),
|
||||
"resting_calories": rmr_metrics.get("resting_calories", 1500),
|
||||
"neat_calories": rmr_metrics.get("neat_calories", 375),
|
||||
"weight_loss_calories": rmr_metrics.get("weight_loss_calories", 500),
|
||||
"weight_loss_rate": rmr_metrics.get("weight_loss_rate", 1.0),
|
||||
"total_calories": rmr_metrics.get("total_calories", 1375),
|
||||
}
|
||||
if 5 in pages_to_generate:
|
||||
contexts["page_5"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": 5,
|
||||
"metabolism_chart": graphs.get("metabolism_chart", ""),
|
||||
"fuel_source_chart": graphs.get("fuel_source_chart", ""),
|
||||
"resting_calories": rmr_metrics.get("resting_calories", 1500),
|
||||
"neat_calories": rmr_metrics.get("neat_calories", 375),
|
||||
"weight_loss_calories": rmr_metrics.get("weight_loss_calories", 500),
|
||||
"weight_loss_rate": rmr_metrics.get("weight_loss_rate", 1.0),
|
||||
"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,
|
||||
)
|
||||
)
|
||||
|
||||
# Calculate FEV1 percentage for page 7
|
||||
fev1_percentage = 0
|
||||
if spirometry_metrics.get("fvc_best"):
|
||||
fev1_percentage = (
|
||||
pnoe_metrics["peak_vt"] / spirometry_metrics["fvc_best"]
|
||||
) * 100
|
||||
# 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%",
|
||||
}
|
||||
|
||||
# Page 7
|
||||
contexts["page_7"] = {
|
||||
# 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"):
|
||||
fev1_percentage = (
|
||||
pnoe_metrics["peak_vt"] / spirometry_metrics["fvc_best"]
|
||||
) * 100
|
||||
|
||||
# Page 7
|
||||
contexts["page_7"] = {
|
||||
"peak_vt": f"{pnoe_metrics['peak_vt']:.2f}",
|
||||
"peak_vt_bpm": f"{int(pnoe_metrics['peak_vt_hr'])}",
|
||||
"fev1_percentage": f"{fev1_percentage:.1f}",
|
||||
@@ -1410,32 +1514,60 @@ class ContextGenerator:
|
||||
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):
|
||||
page_num = i + 13
|
||||
contexts[f"page_{page_num}"] = {
|
||||
# Pages 14-18 (previously 13-17)
|
||||
for i in range(1, 6):
|
||||
page_num = i + 13
|
||||
contexts[f"page_{page_num}"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": page_num,
|
||||
}
|
||||
# Add next_testing_date to page 16
|
||||
if page_num == 16:
|
||||
contexts["page_16"]["next_testing_date"] = self.patient_info.get(
|
||||
"next_testing_date", "Contact us for scheduling"
|
||||
)
|
||||
|
||||
# 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": page_num,
|
||||
}
|
||||
# Add next_testing_date to page 16
|
||||
if page_num == 16:
|
||||
contexts["page_16"]["next_testing_date"] = self.patient_info.get(
|
||||
"page_number": 16,
|
||||
"next_testing_date": self.patient_info.get(
|
||||
"next_testing_date", "Contact us for scheduling"
|
||||
)
|
||||
),
|
||||
}
|
||||
|
||||
# Page 19 - Glossary with Body Fat Percentage Master Chart (previously page 18)
|
||||
contexts["page_19"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": 19,
|
||||
"body_fat_percentage_chart": graphs.get(
|
||||
"body_fat_percentage_master_chart", ""
|
||||
),
|
||||
}
|
||||
# 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 20 (previously page 19)
|
||||
contexts["page_20"] = {
|
||||
"patient_name": self.patient_info["name"],
|
||||
"page_number": 20,
|
||||
}
|
||||
# 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,
|
||||
"body_fat_percentage_chart": graphs.get(
|
||||
"body_fat_percentage_master_chart", ""
|
||||
),
|
||||
}
|
||||
|
||||
# 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:
|
||||
|
||||
Reference in New Issue
Block a user