Add HTML templates for medical report generator with navigation, upload, edit, and preview functionalities

- Created base template with navigation and layout structure
- Implemented upload.html for patient data and file uploads
- Developed edit.html for editing calculated metrics
- Added preview.html for displaying generated report previews
- Enhanced user experience with Tailwind CSS styling
This commit is contained in:
bolade
2025-11-17 17:15:44 +01:00
parent 4f97691ff9
commit 83f50882e2
14 changed files with 1726 additions and 1770 deletions
+102 -27
View File
@@ -101,41 +101,108 @@ class ContextGenerator:
}
return self.patient_info
def calculate_spirometry_metrics(self) -> Dict:
def calculate_spirometry_metrics(self, metric_overrides: Optional[Dict] = None) -> Dict:
"""Calculate spirometry-related metrics"""
if metric_overrides is None:
metric_overrides = {}
metrics = {}
for param in ["FVC", "FEV1", "FEV1/FVC%"]:
row = self.spirometry_df.loc[
self.spirometry_df["Parameters"].str.strip() == param
]
if not row.empty:
param_key = param.lower().replace("/", "_").replace("%", "_pct")
metrics[f"{param_key}_best"] = row["Best"].values[0]
metrics[f"{param_key}_pred"] = row["%Pred."].values[0]
param_key = param.lower().replace("/", "_").replace("%", "_pct")
if f"{param_key}_best" in metric_overrides:
metrics[f"{param_key}_best"] = float(metric_overrides[f"{param_key}_best"])
else:
row = self.spirometry_df.loc[
self.spirometry_df["Parameters"].str.strip() == param
]
if not row.empty:
metrics[f"{param_key}_best"] = row["Best"].values[0]
if f"{param_key}_pred" in metric_overrides:
metrics[f"{param_key}_pred"] = float(metric_overrides[f"{param_key}_pred"])
else:
row = self.spirometry_df.loc[
self.spirometry_df["Parameters"].str.strip() == param
]
if not row.empty:
metrics[f"{param_key}_pred"] = row["%Pred."].values[0]
return metrics
def calculate_pnoe_metrics(self) -> Dict:
def calculate_pnoe_metrics(self, metric_overrides: Optional[Dict] = None) -> Dict:
"""Calculate all Pnoe-derived metrics"""
if metric_overrides is None:
metric_overrides = {}
metrics = {}
metrics["vo2_max"] = self.pnoe_df["VO2(ml/min)_smoothed"].max()
metrics["vo2_max_per_kg"] = metrics["vo2_max"] / self.patient_info["weight"]
# VO2 Max metrics
if "vo2_max" in metric_overrides:
metrics["vo2_max"] = float(metric_overrides["vo2_max"])
else:
metrics["vo2_max"] = self.pnoe_df["VO2(ml/min)_smoothed"].max()
if "vo2_max_per_kg" in metric_overrides:
metrics["vo2_max_per_kg"] = float(metric_overrides["vo2_max_per_kg"])
else:
metrics["vo2_max_per_kg"] = metrics["vo2_max"] / self.patient_info["weight"]
peak_vt_idx = self.pnoe_df["VT(l)_smoothed"].idxmax()
peak_vt_row = self.pnoe_df.loc[peak_vt_idx]
metrics["peak_vt"] = peak_vt_row["VT(l)_smoothed"]
metrics["peak_vt_hr"] = peak_vt_row["HR(bpm)_smoothed"]
# Peak VT metrics
if "peak_vt" in metric_overrides:
metrics["peak_vt"] = float(metric_overrides["peak_vt"])
# Need to get HR from override or calculate
if "peak_vt_hr" in metric_overrides:
metrics["peak_vt_hr"] = float(metric_overrides["peak_vt_hr"])
else:
peak_vt_idx = self.pnoe_df["VT(l)_smoothed"].idxmax()
peak_vt_row = self.pnoe_df.loc[peak_vt_idx]
metrics["peak_vt_hr"] = peak_vt_row["HR(bpm)_smoothed"]
else:
peak_vt_idx = self.pnoe_df["VT(l)_smoothed"].idxmax()
peak_vt_row = self.pnoe_df.loc[peak_vt_idx]
metrics["peak_vt"] = peak_vt_row["VT(l)_smoothed"]
metrics["peak_vt_hr"] = peak_vt_row["HR(bpm)_smoothed"]
fat_max_idx = self.pnoe_df["FAT_smoothed"].idxmax()
fat_max_row = self.pnoe_df.loc[fat_max_idx]
metrics["fat_max_value"] = fat_max_row["FAT_smoothed"]
metrics["fat_max_hr"] = fat_max_row["HR(bpm)_smoothed"]
# Fat Max metrics
if "fat_max_value" in metric_overrides:
metrics["fat_max_value"] = float(metric_overrides["fat_max_value"])
if "fat_max_hr" in metric_overrides:
metrics["fat_max_hr"] = float(metric_overrides["fat_max_hr"])
else:
fat_max_idx = self.pnoe_df["FAT_smoothed"].idxmax()
fat_max_row = self.pnoe_df.loc[fat_max_idx]
metrics["fat_max_hr"] = fat_max_row["HR(bpm)_smoothed"]
else:
fat_max_idx = self.pnoe_df["FAT_smoothed"].idxmax()
fat_max_row = self.pnoe_df.loc[fat_max_idx]
metrics["fat_max_value"] = fat_max_row["FAT_smoothed"]
metrics["fat_max_hr"] = fat_max_row["HR(bpm)_smoothed"]
vt1, vt2 = self._detect_thresholds()
metrics["vt1"] = vt1
metrics["vt2"] = vt2
# VT1 and VT2 thresholds
if "vt1" in metric_overrides:
metrics["vt1"] = metric_overrides["vt1"]
else:
vt1, _ = self._detect_thresholds()
metrics["vt1"] = vt1
if "vt2" in metric_overrides:
metrics["vt2"] = metric_overrides["vt2"]
else:
_, vt2 = self._detect_thresholds()
metrics["vt2"] = vt2
zones = self._calculate_hr_zones(vt1, vt2, fat_max_row)
metrics.update(zones)
# Heart rate zones
if any(f"zone{i}_bpm" in metric_overrides for i in range(1, 6)):
for i in range(1, 6):
zone_key = f"zone{i}_bpm"
if zone_key in metric_overrides:
metrics[zone_key] = metric_overrides[zone_key]
else:
fat_max_idx = self.pnoe_df["FAT_smoothed"].idxmax()
fat_max_row = self.pnoe_df.loc[fat_max_idx]
zones = self._calculate_hr_zones(metrics["vt1"], metrics["vt2"], fat_max_row)
metrics.update(zones)
return metrics
def _detect_thresholds(self) -> Tuple[Optional[Dict], Optional[Dict]]:
@@ -195,12 +262,20 @@ class ContextGenerator:
return zones
def generate_all_contexts(
self, patient_name: str, graphs: Dict[str, str]
self, patient_name: str, graphs: Dict[str, str], metric_overrides: Optional[Dict] = None
) -> List[Dict]:
"""Main method to generate all page contexts"""
if metric_overrides is None:
metric_overrides = {}
self.extract_patient_info(patient_name)
spirometry_metrics = self.calculate_spirometry_metrics()
pnoe_metrics = self.calculate_pnoe_metrics()
# Extract metric overrides for spirometry and pnoe
spirometry_overrides = metric_overrides.get("spirometry", {})
pnoe_overrides = metric_overrides.get("pnoe", {})
spirometry_metrics = self.calculate_spirometry_metrics(spirometry_overrides)
pnoe_metrics = self.calculate_pnoe_metrics(pnoe_overrides)
contexts = []
contexts.append(
+3 -2
View File
@@ -6,7 +6,7 @@ It processes data, generates graphs, and creates PDF reports.
"""
from pathlib import Path
from typing import Any, Dict, List
from typing import Any, Dict, List, Optional
import pandas as pd
from jinja2 import Environment, FileSystemLoader
@@ -287,6 +287,7 @@ class ReportGeneratorService:
seca_excel_path: str,
patient_info: Dict[str, Any],
output_filename: str = None,
metric_overrides: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""
Generate complete medical report from uploaded files.
@@ -387,7 +388,7 @@ class ReportGeneratorService:
pnoe_csv_path, str(spirometry_csv_path), seca_excel_path
)
context_list = self.context_generator.generate_all_contexts(
patient_name, graphs_dict
patient_name, graphs_dict, metric_overrides=metric_overrides
)
# Step 5: Calculate analysis metrics