working again stable

This commit is contained in:
2025-08-22 16:36:45 -07:00
parent 9c4e652047
commit 5f0cd85406
14 changed files with 867 additions and 462 deletions

View File

@@ -6,9 +6,10 @@ Migration script to populate new activity fields from Garmin API
import os
import sys
from datetime import datetime
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine, MetaData, Table, text
from sqlalchemy import MetaData, Table, create_engine, text
from sqlalchemy.exc import OperationalError
from sqlalchemy.orm import sessionmaker
# Add the parent directory to the path to import garminsync modules
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
@@ -20,42 +21,61 @@ from garminsync.garmin import GarminClient
def add_columns_to_database():
"""Add new columns to the activities table if they don't exist"""
print("Adding new columns to database...")
# Get database engine
db_path = os.path.join(os.getenv("DATA_DIR", "data"), "garmin.db")
engine = create_engine(f"sqlite:///{db_path}")
try:
# Reflect the existing database schema
metadata = MetaData()
metadata.reflect(bind=engine)
# Get the activities table
activities_table = metadata.tables['activities']
activities_table = metadata.tables["activities"]
# Check if columns already exist
existing_columns = [col.name for col in activities_table.columns]
new_columns = ['activity_type', 'duration', 'distance', 'max_heart_rate', 'avg_power', 'calories']
new_columns = [
"activity_type",
"duration",
"distance",
"max_heart_rate",
"avg_power",
"calories",
]
# Add missing columns
with engine.connect() as conn:
for column_name in new_columns:
if column_name not in existing_columns:
print(f"Adding column {column_name}...")
if column_name in ['distance', 'avg_power']:
conn.execute(text(f"ALTER TABLE activities ADD COLUMN {column_name} REAL"))
elif column_name in ['duration', 'max_heart_rate', 'calories']:
conn.execute(text(f"ALTER TABLE activities ADD COLUMN {column_name} INTEGER"))
if column_name in ["distance", "avg_power"]:
conn.execute(
text(
f"ALTER TABLE activities ADD COLUMN {column_name} REAL"
)
)
elif column_name in ["duration", "max_heart_rate", "calories"]:
conn.execute(
text(
f"ALTER TABLE activities ADD COLUMN {column_name} INTEGER"
)
)
else:
conn.execute(text(f"ALTER TABLE activities ADD COLUMN {column_name} TEXT"))
conn.execute(
text(
f"ALTER TABLE activities ADD COLUMN {column_name} TEXT"
)
)
conn.commit()
print(f"Column {column_name} added successfully")
else:
print(f"Column {column_name} already exists")
print("Database schema updated successfully")
return True
except Exception as e:
print(f"Failed to update database schema: {e}")
return False
@@ -64,11 +84,11 @@ def add_columns_to_database():
def migrate_activities():
"""Migrate activities to populate new fields from Garmin API"""
print("Starting activity migration...")
# First, add columns to database
if not add_columns_to_database():
return False
# Initialize Garmin client
try:
client = GarminClient()
@@ -77,84 +97,90 @@ def migrate_activities():
print(f"Failed to initialize Garmin client: {e}")
# Continue with migration but without Garmin data
client = None
# Get database session
session = get_session()
try:
# Get all activities that need to be updated (those with NULL activity_type)
activities = session.query(Activity).filter(Activity.activity_type.is_(None)).all()
activities = (
session.query(Activity).filter(Activity.activity_type.is_(None)).all()
)
print(f"Found {len(activities)} activities to migrate")
# If no activities found, try to get all activities (in case activity_type column was just added)
if len(activities) == 0:
activities = session.query(Activity).all()
print(f"Found {len(activities)} total activities")
updated_count = 0
error_count = 0
for i, activity in enumerate(activities):
try:
print(f"Processing activity {i+1}/{len(activities)} (ID: {activity.activity_id})")
print(
f"Processing activity {i+1}/{len(activities)} (ID: {activity.activity_id})"
)
# Fetch detailed activity data from Garmin (if client is available)
activity_details = None
if client:
activity_details = client.get_activity_details(activity.activity_id)
# Update activity fields if we have details
if activity_details:
# Update activity fields
activity.activity_type = activity_details.get('activityType', {}).get('typeKey')
activity.activity_type = activity_details.get(
"activityType", {}
).get("typeKey")
# Extract duration in seconds
duration = activity_details.get('summaryDTO', {}).get('duration')
duration = activity_details.get("summaryDTO", {}).get("duration")
if duration is not None:
activity.duration = int(float(duration))
# Extract distance in meters
distance = activity_details.get('summaryDTO', {}).get('distance')
distance = activity_details.get("summaryDTO", {}).get("distance")
if distance is not None:
activity.distance = float(distance)
# Extract max heart rate
max_hr = activity_details.get('summaryDTO', {}).get('maxHR')
max_hr = activity_details.get("summaryDTO", {}).get("maxHR")
if max_hr is not None:
activity.max_heart_rate = int(float(max_hr))
# Extract average power
avg_power = activity_details.get('summaryDTO', {}).get('avgPower')
avg_power = activity_details.get("summaryDTO", {}).get("avgPower")
if avg_power is not None:
activity.avg_power = float(avg_power)
# Extract calories
calories = activity_details.get('summaryDTO', {}).get('calories')
calories = activity_details.get("summaryDTO", {}).get("calories")
if calories is not None:
activity.calories = int(float(calories))
else:
# Set default values for activity type if we can't get details
activity.activity_type = "Unknown"
# Update last sync timestamp
activity.last_sync = datetime.now().isoformat()
session.commit()
updated_count += 1
# Print progress every 10 activities
if (i + 1) % 10 == 0:
print(f" Progress: {i+1}/{len(activities)} activities processed")
except Exception as e:
print(f" Error processing activity {activity.activity_id}: {e}")
session.rollback()
error_count += 1
continue
print(f"Migration completed. Updated: {updated_count}, Errors: {error_count}")
return True # Allow partial success
except Exception as e:
print(f"Migration failed: {e}")
return False