diff --git a/app/main.py b/app/main.py index 67acd05..a9c9e8a 100644 --- a/app/main.py +++ b/app/main.py @@ -9,6 +9,7 @@ import os import shutil import tempfile import uuid +from datetime import datetime from pathlib import Path from fastapi import FastAPI, File, Form, HTTPException, Request, UploadFile @@ -109,7 +110,6 @@ async def upload_files( gender: str = Form(...), fat_percentage: float = Form(...), 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(...), @@ -179,6 +179,10 @@ async def upload_files( # Prepare patient information patient_name = f"{first_name} {last_name}" print(f"DEBUG: Received next_testing_date: '{next_testing_date}'") + + # Generate session_id internally using timestamp for unique identification + session_id = datetime.now().strftime("%Y%m%d_%H%M%S") + patient_info = { "patient_name": patient_name, "first_name": first_name, @@ -290,8 +294,18 @@ async def upload_files( @app.get("/preview", response_class=HTMLResponse) async def preview(request: Request): """Preview generated report""" + # Check for required session data if not request.session.get("report_path"): return RedirectResponse(url="/", status_code=303) + + # Ensure metrics exist in session, initialize if missing + if "metrics" not in request.session: + request.session["metrics"] = {"pnoe": {}, "spirometry": {}} + + # Ensure patient_info exists + if "patient_info" not in request.session: + request.session["patient_info"] = {} + return render_template( "preview.html", {"request": request, "session": request.session} ) @@ -309,8 +323,16 @@ async def serve_graph(filename: str): @app.get("/edit", response_class=HTMLResponse) async def edit_form(request: Request): """Display edit metrics form""" - if not request.session.get("metrics"): + # Check for required session data + if not request.session.get("report_path") or not request.session.get( + "patient_info" + ): return RedirectResponse(url="/", status_code=303) + + # Ensure metrics exist in session, initialize if missing + if "metrics" not in request.session: + request.session["metrics"] = {"pnoe": {}, "spirometry": {}} + return render_template( "edit.html", {"request": request, "session": request.session} ) @@ -325,69 +347,117 @@ async def edit_metrics(request: Request): # Get form data form_data = await request.form() + # Helper function to safely convert form values to float + def safe_float(value): + """Convert form value to float, return None if empty or invalid""" + if not value or value.strip() == "": + return None + try: + return float(value) + except (ValueError, TypeError): + return None + # Build metric overrides metric_overrides = {"pnoe": {}, "spirometry": {}} - # Pnoe overrides - if form_data.get("vo2_max"): - metric_overrides["pnoe"]["vo2_max"] = float(form_data["vo2_max"]) - if form_data.get("vo2_max_per_kg"): - metric_overrides["pnoe"]["vo2_max_per_kg"] = float(form_data["vo2_max_per_kg"]) - if form_data.get("peak_vt"): - metric_overrides["pnoe"]["peak_vt"] = float(form_data["peak_vt"]) - if form_data.get("peak_vt_hr"): - metric_overrides["pnoe"]["peak_vt_hr"] = float(form_data["peak_vt_hr"]) - if form_data.get("fat_max_value"): - metric_overrides["pnoe"]["fat_max_value"] = float(form_data["fat_max_value"]) - if form_data.get("fat_max_hr"): - metric_overrides["pnoe"]["fat_max_hr"] = float(form_data["fat_max_hr"]) + # Pnoe overrides - only add if value is provided and valid + vo2_max_val = safe_float(form_data.get("vo2_max")) + if vo2_max_val is not None: + metric_overrides["pnoe"]["vo2_max"] = vo2_max_val - # VT1 and VT2 overrides - if ( - form_data.get("vt1_hr") - or form_data.get("vt1_speed") - or form_data.get("vt1_time") - ): - metric_overrides["pnoe"]["vt1"] = { - "HeartRate": float(form_data.get("vt1_hr", 0)), - "Speed": float(form_data.get("vt1_speed", 0)), - "Time": float(form_data.get("vt1_time", 0)), + vo2_max_per_kg_val = safe_float(form_data.get("vo2_max_per_kg")) + if vo2_max_per_kg_val is not None: + metric_overrides["pnoe"]["vo2_max_per_kg"] = vo2_max_per_kg_val + + peak_vt_val = safe_float(form_data.get("peak_vt")) + if peak_vt_val is not None: + metric_overrides["pnoe"]["peak_vt"] = peak_vt_val + + peak_vt_hr_val = safe_float(form_data.get("peak_vt_hr")) + if peak_vt_hr_val is not None: + metric_overrides["pnoe"]["peak_vt_hr"] = peak_vt_hr_val + + fat_max_value_val = safe_float(form_data.get("fat_max_value")) + if fat_max_value_val is not None: + metric_overrides["pnoe"]["fat_max_value"] = fat_max_value_val + + fat_max_hr_val = safe_float(form_data.get("fat_max_hr")) + if fat_max_hr_val is not None: + metric_overrides["pnoe"]["fat_max_hr"] = fat_max_hr_val + + # VT1 and VT2 overrides - use existing values if not provided + existing_metrics = request.session.get("metrics", {}) + existing_pnoe = existing_metrics.get("pnoe", {}) + existing_vt1 = existing_pnoe.get("vt1", {}) + existing_vt2 = existing_pnoe.get("vt2", {}) + + vt1_hr_val = safe_float(form_data.get("vt1_hr")) + vt1_speed_val = safe_float(form_data.get("vt1_speed")) + vt1_time_val = safe_float(form_data.get("vt1_time")) + + if vt1_hr_val is not None or vt1_speed_val is not None or vt1_time_val is not None: + vt1_dict = { + "HeartRate": vt1_hr_val + if vt1_hr_val is not None + else existing_vt1.get("HeartRate", 0), + "Speed": vt1_speed_val + if vt1_speed_val is not None + else existing_vt1.get("Speed", 0), + "Time": vt1_time_val + if vt1_time_val is not None + else existing_vt1.get("Time", 0), } + metric_overrides["pnoe"]["vt1"] = vt1_dict - if ( - form_data.get("vt2_hr") - or form_data.get("vt2_speed") - or form_data.get("vt2_time") - ): - metric_overrides["pnoe"]["vt2"] = { - "HeartRate": float(form_data.get("vt2_hr", 0)), - "Speed": float(form_data.get("vt2_speed", 0)), - "Time": float(form_data.get("vt2_time", 0)), + vt2_hr_val = safe_float(form_data.get("vt2_hr")) + vt2_speed_val = safe_float(form_data.get("vt2_speed")) + vt2_time_val = safe_float(form_data.get("vt2_time")) + + if vt2_hr_val is not None or vt2_speed_val is not None or vt2_time_val is not None: + vt2_dict = { + "HeartRate": vt2_hr_val + if vt2_hr_val is not None + else existing_vt2.get("HeartRate", 0), + "Speed": vt2_speed_val + if vt2_speed_val is not None + else existing_vt2.get("Speed", 0), + "Time": vt2_time_val + if vt2_time_val is not None + else existing_vt2.get("Time", 0), } + metric_overrides["pnoe"]["vt2"] = vt2_dict - # Heart rate zones + # Heart rate zones - only add if value is provided for i in range(1, 6): zone_key = f"zone{i}_bpm" - if form_data.get(zone_key): - metric_overrides["pnoe"][zone_key] = form_data[zone_key] + zone_val = form_data.get(zone_key) + if zone_val and zone_val.strip(): + metric_overrides["pnoe"][zone_key] = zone_val.strip() - # Spirometry overrides - if form_data.get("fvc_best"): - metric_overrides["spirometry"]["fvc_best"] = float(form_data["fvc_best"]) - if form_data.get("fvc_pred"): - metric_overrides["spirometry"]["fvc_pred"] = float(form_data["fvc_pred"]) - if form_data.get("fev1_best"): - metric_overrides["spirometry"]["fev1_best"] = float(form_data["fev1_best"]) - if form_data.get("fev1_pred"): - metric_overrides["spirometry"]["fev1_pred"] = float(form_data["fev1_pred"]) - if form_data.get("fev1_fvc_pct_best"): - metric_overrides["spirometry"]["fev1_fvc_pct_best"] = float( - form_data["fev1_fvc_pct_best"] - ) - if form_data.get("fev1_fvc_pct_pred"): - metric_overrides["spirometry"]["fev1_fvc_pct_pred"] = float( - form_data["fev1_fvc_pct_pred"] - ) + # Spirometry overrides - only add if value is provided and valid + fvc_best_val = safe_float(form_data.get("fvc_best")) + if fvc_best_val is not None: + metric_overrides["spirometry"]["fvc_best"] = fvc_best_val + + fvc_pred_val = safe_float(form_data.get("fvc_pred")) + if fvc_pred_val is not None: + metric_overrides["spirometry"]["fvc_pred"] = fvc_pred_val + + fev1_best_val = safe_float(form_data.get("fev1_best")) + if fev1_best_val is not None: + metric_overrides["spirometry"]["fev1_best"] = fev1_best_val + + fev1_pred_val = safe_float(form_data.get("fev1_pred")) + if fev1_pred_val is not None: + metric_overrides["spirometry"]["fev1_pred"] = fev1_pred_val + + fev1_fvc_pct_best_val = safe_float(form_data.get("fev1_fvc_pct_best")) + if fev1_fvc_pct_best_val is not None: + metric_overrides["spirometry"]["fev1_fvc_pct_best"] = fev1_fvc_pct_best_val + + fev1_fvc_pct_pred_val = safe_float(form_data.get("fev1_fvc_pct_pred")) + if fev1_fvc_pct_pred_val is not None: + metric_overrides["spirometry"]["fev1_fvc_pct_pred"] = fev1_fvc_pct_pred_val try: # Get file paths from session @@ -468,6 +538,7 @@ async def edit_metrics(request: Request): "fat_percentage": patient_info.get("fat_percentage", 0), "gender": patient_info.get("gender", "female"), } + # Calculate fat_mass and lean_mass (extract_patient_info does this when no SECA file) context_gen.extract_patient_info(patient_info.get("last_name", "")) spirometry_overrides = metric_overrides.get("spirometry", {}) @@ -514,7 +585,6 @@ async def generate_report( height: str = Form(..., description="Patient height (e.g., 5'4\")"), weight: str = Form(..., description="Patient weight (e.g., 123lbs)"), focus: str = Form(default="Endurance", description="Training focus"), - session_id: str = Form(default="default", description="Session ID"), spirometry_pdf: UploadFile = File(..., description="Spirometry PDF file"), pnoe_csv: UploadFile = File(..., description="Pnoe CSV file"), seca_excel: UploadFile = File(..., description="SECA Excel file"), @@ -534,7 +604,6 @@ async def generate_report( height: Patient height weight: Patient weight focus: Training focus (default: Endurance) - session_id: Session identifier (default: default) Returns: ReportResponse with report path, graphs generated, and analysis data @@ -571,6 +640,9 @@ async def generate_report( with open(seca_path, "wb") as f: shutil.copyfileobj(seca_excel.file, f) + # Generate session_id internally using timestamp for unique identification + session_id = datetime.now().strftime("%Y%m%d_%H%M%S") + # Prepare patient information patient_info = { "patient_name": patient_name, diff --git a/app/report_gen/page_19_20_minimal.html b/app/report_gen/page_19_20_minimal.html index ed0c0f8..8906f85 100644 --- a/app/report_gen/page_19_20_minimal.html +++ b/app/report_gen/page_19_20_minimal.html @@ -471,3 +471,4 @@ + diff --git a/app/report_gen/page_2_minimal.html b/app/report_gen/page_2_minimal.html index 50d90c6..eb21fe1 100644 --- a/app/report_gen/page_2_minimal.html +++ b/app/report_gen/page_2_minimal.html @@ -86,3 +86,4 @@ + diff --git a/app/report_gen/page_6.html b/app/report_gen/page_6.html index 709975d..6383397 100644 --- a/app/report_gen/page_6.html +++ b/app/report_gen/page_6.html @@ -34,7 +34,7 @@
- {{ deficit_calories | default('1725KCals') }} + {{ deficit_calories | default('1725KCals') }} KCals
{{ deficit_protein | default('120g Protein') }}
diff --git a/app/services/__pycache__/context_generator.cpython-312.pyc b/app/services/__pycache__/context_generator.cpython-312.pyc index c159332..80bbd60 100644 Binary files a/app/services/__pycache__/context_generator.cpython-312.pyc and b/app/services/__pycache__/context_generator.cpython-312.pyc differ diff --git a/app/services/__pycache__/report_generator.cpython-312.pyc b/app/services/__pycache__/report_generator.cpython-312.pyc index 32ff5d8..4e782b3 100644 Binary files a/app/services/__pycache__/report_generator.cpython-312.pyc and b/app/services/__pycache__/report_generator.cpython-312.pyc differ diff --git a/app/templates/preview.html b/app/templates/preview.html index 9265a80..f6bcd3c 100644 --- a/app/templates/preview.html +++ b/app/templates/preview.html @@ -1,24 +1,32 @@ -{% extends "base.html" %} - -{% block title %}Report Preview - Report Generator{% endblock %} - -{% block content %} +{% extends "base.html" %} {% block title %}Report Preview - Report Generator{% +endblock %} {% block content %}
{% if not session.get('report_path') %}
-

No report found. Please upload files first.

+

+ No report found. Please + upload files first. +

{% else %} - +
-

Generated Report Preview

+

+ Generated Report Preview +

@@ -26,23 +34,33 @@
-

Patient Information

+

+ Patient Information +

Name

-

{{ session.patient_info['patient_name'] }}

+

+ {{ session.patient_info['patient_name'] }} +

Age

-

{{ session.patient_info['age'] }}

+

+ {{ session.patient_info['age'] }} +

Height

-

{{ session.patient_info['height'] }}

+

+ {{ session.patient_info['height'] }} +

Weight

-

{{ session.patient_info['weight'] }}

+

+ {{ session.patient_info['weight'] }} +

@@ -52,56 +70,113 @@
-

Pnoe Metrics

-
+

+ Pnoe Metrics +

+
{% if session.metrics.pnoe.get('vo2_max') %}

VO2 Max

-

{{ "%.2f"|format(session.metrics.pnoe['vo2_max']) }} ml/min

+

+ {{ + "%.2f"|format(session.metrics.pnoe['vo2_max']) + }} ml/min +

- {% endif %} - {% if session.metrics.pnoe.get('vo2_max_per_kg') %} + {% endif %} {% if + session.metrics.pnoe.get('vo2_max_per_kg') %}

VO2 Max per kg

-

{{ "%.2f"|format(session.metrics.pnoe['vo2_max_per_kg']) }} ml/min/kg

+

+ {{ + "%.2f"|format(session.metrics.pnoe['vo2_max_per_kg']) + }} ml/min/kg +

- {% endif %} - {% if session.metrics.pnoe.get('peak_vt') %} + {% endif %} {% if session.metrics.pnoe.get('peak_vt') %}

Peak VT

-

{{ "%.2f"|format(session.metrics.pnoe['peak_vt']) }} L

-

HR: {{ "%.0f"|format(session.metrics.pnoe['peak_vt_hr']) }} bpm

+

+ {{ + "%.2f"|format(session.metrics.pnoe['peak_vt']) + }} L +

+

+ HR: {{ + "%.0f"|format(session.metrics.pnoe['peak_vt_hr']) + }} bpm +

- {% endif %} - {% if session.metrics.pnoe.get('fat_max_value') %} + {% endif %} {% if + session.metrics.pnoe.get('fat_max_value') %}

Fat Max Value

-

{{ "%.2f"|format(session.metrics.pnoe['fat_max_value']) }} kcal/min

-

HR: {{ "%.0f"|format(session.metrics.pnoe['fat_max_hr']) }} bpm

+

+ {{ + "%.2f"|format(session.metrics.pnoe['fat_max_value']) + }} kcal/min +

+

+ HR: {{ + "%.0f"|format(session.metrics.pnoe['fat_max_hr']) + }} bpm +

{% endif %}
- {% if session.metrics.pnoe.get('vt1') or session.metrics.pnoe.get('vt2') %} + {% if session.metrics.pnoe.get('vt1') or + session.metrics.pnoe.get('vt2') %}
-

Ventilatory Thresholds

+

+ Ventilatory Thresholds +

{% if session.metrics.pnoe.get('vt1') %}
-

VT1

-

Heart Rate: {{ "%.0f"|format(session.metrics.pnoe['vt1']['HeartRate']) }} bpm

-

Speed: {{ "%.2f"|format(session.metrics.pnoe['vt1']['Speed']) }} mph

-

Time: {{ "%.0f"|format(session.metrics.pnoe['vt1']['Time']) }} sec

+

+ VT1 +

+

+ Heart Rate: {{ + "%.0f"|format(session.metrics.pnoe['vt1']['HeartRate']) + }} bpm +

+

+ Speed: {{ + "%.2f"|format(session.metrics.pnoe['vt1']['Speed']) + }} mph +

+

+ Time: {{ + "%.0f"|format(session.metrics.pnoe['vt1']['Time']) + }} sec +

- {% endif %} - {% if session.metrics.pnoe.get('vt2') %} + {% endif %} {% if session.metrics.pnoe.get('vt2') %}
-

VT2

-

Heart Rate: {{ "%.0f"|format(session.metrics.pnoe['vt2']['HeartRate']) }} bpm

-

Speed: {{ "%.2f"|format(session.metrics.pnoe['vt2']['Speed']) }} mph

-

Time: {{ "%.0f"|format(session.metrics.pnoe['vt2']['Time']) }} sec

+

+ VT2 +

+

+ Heart Rate: {{ + "%.0f"|format(session.metrics.pnoe['vt2']['HeartRate']) + }} bpm +

+

+ Speed: {{ + "%.2f"|format(session.metrics.pnoe['vt2']['Speed']) + }} mph +

+

+ Time: {{ + "%.0f"|format(session.metrics.pnoe['vt2']['Time']) + }} sec +

{% endif %}
@@ -111,17 +186,20 @@ {% if session.metrics.pnoe.get('zone1_bpm') %}
-

Heart Rate Zones

+

+ Heart Rate Zones +

- {% for i in range(1, 6) %} - {% set zone_key = "zone" + i|string + "_bpm" %} - {% if session.metrics.pnoe.get(zone_key) %} + {% for i in range(1, 6) %} {% set zone_key = "zone" + + i|string + "_bpm" %} {% if + session.metrics.pnoe.get(zone_key) %}

Zone {{ i }}

-

{{ session.metrics.pnoe[zone_key] }}

+

+ {{ session.metrics.pnoe[zone_key] }} +

- {% endif %} - {% endfor %} + {% endif %} {% endfor %}
{% endif %} @@ -129,27 +207,53 @@ {% if session.metrics.spirometry %}
-

Spirometry Metrics

+

+ Spirometry Metrics +

{% if session.metrics.spirometry.get('fvc_best') %}

FVC Best

-

{{ "%.2f"|format(session.metrics.spirometry['fvc_best']) }} L

-

{{ "%.1f"|format(session.metrics.spirometry['fvc_pred']) }}% predicted

+

+ {{ + "%.2f"|format(session.metrics.spirometry['fvc_best']) + }} L +

+

+ {{ + "%.1f"|format(session.metrics.spirometry['fvc_pred']) + }}% predicted +

- {% endif %} - {% if session.metrics.spirometry.get('fev1_best') %} + {% endif %} {% if + session.metrics.spirometry.get('fev1_best') %}

FEV1 Best

-

{{ "%.2f"|format(session.metrics.spirometry['fev1_best']) }} L

-

{{ "%.1f"|format(session.metrics.spirometry['fev1_pred']) }}% predicted

+

+ {{ + "%.2f"|format(session.metrics.spirometry['fev1_best']) + }} L +

+

+ {{ + "%.1f"|format(session.metrics.spirometry['fev1_pred']) + }}% predicted +

- {% endif %} - {% if session.metrics.spirometry.get('fev1_fvc_pct_best') %} + {% endif %} {% if + session.metrics.spirometry.get('fev1_fvc_pct_best') %}

FEV1/FVC%

-

{{ "%.2f"|format(session.metrics.spirometry['fev1_fvc_pct_best']) }}%

-

{{ "%.1f"|format(session.metrics.spirometry['fev1_fvc_pct_pred']) }}% predicted

+

+ {{ + "%.2f"|format(session.metrics.spirometry['fev1_fvc_pct_best']) + }}% +

+

+ {{ + "%.1f"|format(session.metrics.spirometry['fev1_fvc_pct_pred']) + }}% predicted +

{% endif %}
@@ -157,24 +261,8 @@ {% endif %}
{% endif %} - - - {% if session.graphs_generated %} -
-

Generated Graphs

-
- {% for graph in session.graphs_generated %} -
-

{{ graph.name|replace('_', ' ')|title }}

- {{ graph.name }} -
- {% endfor %} -
-
- {% endif %}
{% endif %}
{% endblock %} - diff --git a/app/templates/upload.html b/app/templates/upload.html index fb44f06..4c1374b 100644 --- a/app/templates/upload.html +++ b/app/templates/upload.html @@ -132,20 +132,6 @@ 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" />
-
- - -