""" Health Metrics Data Layer Provides structured data for vital signs and health monitoring. Functions: - get_resting_heart_rate_data(): Average RHR with trend - get_heart_rate_variability_data(): Average HRV with trend - get_vo2_max_data(): Latest VO2 Max value All functions return structured data (dict) without formatting. Use placeholder_resolver.py for formatted strings for AI. Phase 0c: Multi-Layer Architecture Version: 1.0 """ from typing import Dict, List, Optional from datetime import datetime, timedelta, date from db import get_db, get_cursor, r2d from data_layer.utils import calculate_confidence, safe_float, safe_int def get_resting_heart_rate_data( profile_id: str, days: int = 7 ) -> Dict: """ Get average resting heart rate with trend. Args: profile_id: User profile ID days: Analysis window (default 7) Returns: { "avg_rhr": int, # beats per minute "min_rhr": int, "max_rhr": int, "measurements": int, "confidence": str, "days_analyzed": int } Migration from Phase 0b: OLD: get_vitals_avg_hr(pid, days) formatted string NEW: Structured data with min/max """ with get_db() as conn: cur = get_cursor(conn) cutoff = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d') cur.execute( """SELECT AVG(resting_hr) as avg, MIN(resting_hr) as min, MAX(resting_hr) as max, COUNT(*) as count FROM vitals_baseline WHERE profile_id=%s AND date >= %s AND resting_hr IS NOT NULL""", (profile_id, cutoff) ) row = cur.fetchone() if not row or row['count'] == 0: return { "avg_rhr": 0, "min_rhr": 0, "max_rhr": 0, "measurements": 0, "confidence": "insufficient", "days_analyzed": days } measurements = row['count'] confidence = calculate_confidence(measurements, days, "general") return { "avg_rhr": safe_int(row['avg']), "min_rhr": safe_int(row['min']), "max_rhr": safe_int(row['max']), "measurements": measurements, "confidence": confidence, "days_analyzed": days } def get_heart_rate_variability_data( profile_id: str, days: int = 7 ) -> Dict: """ Get average heart rate variability with trend. Args: profile_id: User profile ID days: Analysis window (default 7) Returns: { "avg_hrv": int, # milliseconds "min_hrv": int, "max_hrv": int, "measurements": int, "confidence": str, "days_analyzed": int } Migration from Phase 0b: OLD: get_vitals_avg_hrv(pid, days) formatted string NEW: Structured data with min/max """ with get_db() as conn: cur = get_cursor(conn) cutoff = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d') cur.execute( """SELECT AVG(hrv) as avg, MIN(hrv) as min, MAX(hrv) as max, COUNT(*) as count FROM vitals_baseline WHERE profile_id=%s AND date >= %s AND hrv IS NOT NULL""", (profile_id, cutoff) ) row = cur.fetchone() if not row or row['count'] == 0: return { "avg_hrv": 0, "min_hrv": 0, "max_hrv": 0, "measurements": 0, "confidence": "insufficient", "days_analyzed": days } measurements = row['count'] confidence = calculate_confidence(measurements, days, "general") return { "avg_hrv": safe_int(row['avg']), "min_hrv": safe_int(row['min']), "max_hrv": safe_int(row['max']), "measurements": measurements, "confidence": confidence, "days_analyzed": days } def get_vo2_max_data( profile_id: str ) -> Dict: """ Get latest VO2 Max value with date. Args: profile_id: User profile ID Returns: { "vo2_max": float, # ml/kg/min "date": date, "confidence": str } Migration from Phase 0b: OLD: get_vitals_vo2_max(pid) formatted string NEW: Structured data with date """ with get_db() as conn: cur = get_cursor(conn) cur.execute( """SELECT vo2_max, date FROM vitals_baseline WHERE profile_id=%s AND vo2_max IS NOT NULL ORDER BY date DESC LIMIT 1""", (profile_id,) ) row = cur.fetchone() if not row: return { "vo2_max": 0.0, "date": None, "confidence": "insufficient" } return { "vo2_max": safe_float(row['vo2_max']), "date": row['date'], "confidence": "high" }