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:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user