diff --git a/backend/routers/blood_pressure.py b/backend/routers/blood_pressure.py index 92bb28d..e22c790 100644 --- a/backend/routers/blood_pressure.py +++ b/backend/routers/blood_pressure.py @@ -310,8 +310,15 @@ async def import_omron_csv( with get_db() as conn: cur = get_cursor(conn) + # Log available columns for debugging + first_row = True + for row in reader: try: + if first_row: + logger.info(f"Omron CSV Columns: {list(row.keys())}") + first_row = False + # Parse Omron German date format date_str = row.get('Datum', row.get('Date')) time_str = row.get('Zeit', row.get('Time', '08:00')) @@ -325,18 +332,27 @@ async def import_omron_csv( errors += 1 continue - # Extract measurements - systolic = row.get('Systolisch', row.get('Systolic')) - diastolic = row.get('Diastolisch', row.get('Diastolic')) - pulse = row.get('Puls', row.get('Pulse')) + # Extract measurements (support column names with/without units) + systolic = (row.get('Systolisch (mmHg)') or row.get('Systolisch') or + row.get('Systolic (mmHg)') or row.get('Systolic')) + diastolic = (row.get('Diastolisch (mmHg)') or row.get('Diastolisch') or + row.get('Diastolic (mmHg)') or row.get('Diastolic')) + pulse = (row.get('Puls (bpm)') or row.get('Puls') or + row.get('Pulse (bpm)') or row.get('Pulse')) if not systolic or not diastolic: + logger.warning(f"Skipped row {date_str} {time_str}: Missing BP values (sys={systolic}, dia={diastolic})") skipped += 1 continue - # Parse warning flags - irregular = row.get('Unregelmäßiger Herzschlag', row.get('Irregular Heartbeat', '')) - afib = row.get('Vorhofflimmern', row.get('AFib', '')) + # Parse warning flags (support various column names) + irregular = (row.get('Unregelmäßiger Herzschlag festgestellt') or + row.get('Unregelmäßiger Herzschlag') or + row.get('Irregular Heartbeat') or '') + afib = (row.get('Mögliches AFib') or + row.get('Vorhofflimmern') or + row.get('Possible AFib') or + row.get('AFib') or '') irregular_heartbeat = irregular.lower() in ['ja', 'yes', 'true', '1'] possible_afib = afib.lower() in ['ja', 'yes', 'true', '1'] @@ -376,7 +392,10 @@ async def import_omron_csv( )) result = cur.fetchone() - if result and result['inserted']: + if result is None: + # WHERE clause prevented update (manual entry exists) + skipped += 1 + elif result['inserted']: inserted += 1 else: updated += 1 diff --git a/backend/routers/vitals_baseline.py b/backend/routers/vitals_baseline.py index 95b1d5d..cd460ee 100644 --- a/backend/routers/vitals_baseline.py +++ b/backend/routers/vitals_baseline.py @@ -290,6 +290,27 @@ def get_baseline_stats( # Import: Apple Health CSV # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +def safe_int(value): + """Safely parse string to int, handling decimals.""" + if not value or value == '': + return None + try: + # If it has a decimal point, parse as float first then round to int + if '.' in str(value): + return int(float(value)) + return int(value) + except (ValueError, TypeError): + return None + +def safe_float(value): + """Safely parse string to float.""" + if not value or value == '': + return None + try: + return float(value) + except (ValueError, TypeError): + return None + @router.post("/import/apple-health") async def import_apple_health_baseline( file: UploadFile = File(...), @@ -307,26 +328,38 @@ async def import_apple_health_baseline( updated = 0 skipped = 0 errors = 0 + error_details = [] # Collect error messages with get_db() as conn: cur = get_cursor(conn) + # Log available columns for debugging + first_row = True + for row in reader: try: - date = row.get('Start')[:10] if row.get('Start') else None + if first_row: + logger.info(f"CSV Columns: {list(row.keys())}") + first_row = False + + # Support both English and German column names + date_raw = row.get('Start') or row.get('Datum/Uhrzeit') + date = date_raw[:10] if date_raw else None if not date: + logger.warning(f"Skipped row (no date): Start='{row.get('Start')}', Datum/Uhrzeit='{row.get('Datum/Uhrzeit')}'") skipped += 1 continue - # Extract baseline vitals from Apple Health export - rhr = row.get('Resting Heart Rate') - hrv = row.get('Heart Rate Variability') - vo2 = row.get('VO2 Max') - spo2 = row.get('Oxygen Saturation') - resp_rate = row.get('Respiratory Rate') + # Extract baseline vitals (support English + German column names) + rhr = row.get('Resting Heart Rate') or row.get('Ruhepuls (count/min)') + hrv = row.get('Heart Rate Variability') or row.get('Herzfrequenzvariabilität (ms)') + vo2 = row.get('VO2 Max') or row.get('VO2 max (ml/(kg·min))') + spo2 = row.get('Oxygen Saturation') or row.get('Blutsauerstoffsättigung (%)') + resp_rate = row.get('Respiratory Rate') or row.get('Atemfrequenz (count/min)') # Skip if no baseline vitals if not any([rhr, hrv, vo2, spo2, resp_rate]): + logger.warning(f"Skipped row {date} (no vitals): RHR={rhr}, HRV={hrv}, VO2={vo2}, SpO2={spo2}, RespRate={resp_rate}") skipped += 1 continue @@ -349,26 +382,33 @@ async def import_apple_health_baseline( RETURNING (xmax = 0) AS inserted """, ( pid, date, - int(rhr) if rhr else None, - int(hrv) if hrv else None, - float(vo2) if vo2 else None, - int(spo2) if spo2 else None, - float(resp_rate) if resp_rate else None + safe_int(rhr), + safe_int(hrv), + safe_float(vo2), + safe_int(spo2), + safe_float(resp_rate) )) result = cur.fetchone() - if result and result['inserted']: + if result is None: + # WHERE clause prevented update (manual entry exists) + skipped += 1 + elif result['inserted']: inserted += 1 else: updated += 1 except Exception as e: - logger.error(f"Error importing row: {e}") + import traceback + error_msg = f"Row {date if 'date' in locals() else 'unknown'}: {str(e)}" + error_details.append(error_msg) + logger.error(f"{error_msg}\n{traceback.format_exc()}") errors += 1 return { "inserted": inserted, "updated": updated, "skipped": skipped, - "errors": errors + "errors": errors, + "error_details": error_details[:10] # Return first 10 errors } diff --git a/frontend/src/pages/VitalsPage.jsx b/frontend/src/pages/VitalsPage.jsx index d6fda55..6e94708 100644 --- a/frontend/src/pages/VitalsPage.jsx +++ b/frontend/src/pages/VitalsPage.jsx @@ -947,10 +947,18 @@ function ImportTab({ onImportComplete }) { {error &&
{error}
} {result && ( -
- Import erfolgreich ({result.type === 'omron' ? 'Omron' : 'Apple Health'}):
+
0 ? '#FCEBEB' : '#E8F7F0', border: `1px solid ${result.errors > 0 ? '#D85A30' : '#1D9E75'}`, borderRadius: 8, fontSize: 13, color: result.errors > 0 ? '#D85A30' : '#085041', marginBottom: 12 }}> + Import {result.errors > 0 ? 'mit Fehlern' : 'erfolgreich'} ({result.type === 'omron' ? 'Omron' : 'Apple Health'}):
{result.inserted} neu · {result.updated} aktualisiert · {result.skipped} übersprungen {result.errors > 0 && <> · {result.errors} Fehler} + {result.error_details && result.error_details.length > 0 && ( +
+ Fehler-Details: + {result.error_details.map((err, i) => ( +
• {err}
+ ))} +
+ )}
)}