mitai-jinkendo/backend/data_layer/health_metrics.py
Lars b4558b0582
All checks were successful
Deploy Development / deploy (push) Successful in 53s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 14s
feat: Phase 0c - health_metrics.py module complete
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>
2026-03-28 19:15:31 +01:00

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"
}