Data Layer: - get_resting_heart_rate_data() - avg RHR with min/max trend - get_heart_rate_variability_data() - avg HRV with min/max trend - get_vo2_max_data() - latest VO2 Max with date Placeholder Layer: - get_vitals_avg_hr() - refactored to use data layer - get_vitals_avg_hrv() - refactored to use data layer - get_vitals_vo2_max() - refactored to use data layer All 3 health data functions + 3 placeholder refactors complete. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
198 lines
5.2 KiB
Python
198 lines
5.2 KiB
Python
"""
|
|
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"
|
|
}
|