Adding latest changes
This commit is contained in:
@@ -53,6 +53,26 @@ class GarminSync:
|
||||
pass
|
||||
return activities
|
||||
|
||||
def get_last_sync_date(self) -> Any:
|
||||
"""Get the date of the latest stored activity."""
|
||||
activities = self.load_local_activities()
|
||||
if not activities:
|
||||
return None
|
||||
|
||||
latest_date = None
|
||||
for act in activities:
|
||||
start_str = act.get("startTimeLocal")
|
||||
if start_str:
|
||||
try:
|
||||
# Parse "YYYY-MM-DD HH:MM:SS" -> date
|
||||
d = datetime.strptime(start_str.split(" ")[0], "%Y-%m-%d").date()
|
||||
if latest_date is None or d > latest_date:
|
||||
latest_date = d
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
return latest_date
|
||||
|
||||
def sync_smart(self) -> int:
|
||||
"""Sync only new activities since the last local one."""
|
||||
try:
|
||||
@@ -271,6 +291,22 @@ class GarminSync:
|
||||
label = k.replace("_", " ").title()
|
||||
breakdown_list.append({"label": label, "count": v})
|
||||
|
||||
# VO2 Max Logic
|
||||
sorted_acts = sorted(
|
||||
activities,
|
||||
key=lambda x: x.get("startTimeLocal", ""),
|
||||
reverse=True
|
||||
)
|
||||
|
||||
vo2_max = None
|
||||
for act in sorted_acts:
|
||||
if "vo2MaxValue" in act:
|
||||
vo2_max = act["vo2MaxValue"]
|
||||
break
|
||||
if "vo2MaxCyclingValue" in act:
|
||||
vo2_max = act["vo2MaxCyclingValue"]
|
||||
break
|
||||
|
||||
return {
|
||||
"summary": {
|
||||
"total_hours": round(current_period["hours"], 1),
|
||||
@@ -278,5 +314,6 @@ class GarminSync:
|
||||
"period_label": "Last 7 Days"
|
||||
},
|
||||
"breakdown": breakdown_list,
|
||||
"strength_sessions": strength_count
|
||||
"strength_sessions": strength_count,
|
||||
"vo2_max": vo2_max
|
||||
}
|
||||
|
||||
+7
-1
@@ -107,10 +107,16 @@ async def get_settings_status():
|
||||
env.load_service_env("withings")
|
||||
env.load_service_env("gemini")
|
||||
|
||||
|
||||
# Get last sync date
|
||||
sync = GarminSync(None, storage_dir=get_storage_path("garmin"))
|
||||
last_date = sync.get_last_sync_date()
|
||||
|
||||
return {
|
||||
"garmin": {
|
||||
"configured": bool(os.getenv("GARMIN_EMAIL") and os.getenv("GARMIN_PASSWORD")),
|
||||
"email": os.getenv("GARMIN_EMAIL")
|
||||
"email": os.getenv("GARMIN_EMAIL"),
|
||||
"last_synced_workout": last_date.strftime("%d.%m.%Y") if last_date else None
|
||||
},
|
||||
"withings": {
|
||||
"configured": bool(os.getenv("WITHINGS_CLIENT_ID") and os.getenv("WITHINGS_CLIENT_SECRET"))
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from typing import Dict, Any
|
||||
|
||||
# Ensure we can import from src
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from common.env_manager import EnvManager
|
||||
from garmin.workout_manager import WorkoutManager
|
||||
|
||||
def load_sample_workout() -> Dict[str, Any]:
|
||||
"""Create a minimal valid workout for testing."""
|
||||
return {
|
||||
"workoutName": "Test Workout",
|
||||
"description": "Base for AI test",
|
||||
"sportType": {
|
||||
"sportTypeId": 1,
|
||||
"sportTypeKey": "running"
|
||||
},
|
||||
"workoutSegments": [
|
||||
{
|
||||
"segmentOrder": 1,
|
||||
"sportType": {
|
||||
"sportTypeId": 1,
|
||||
"sportTypeKey": "running"
|
||||
},
|
||||
"workoutSteps": [
|
||||
{
|
||||
"type": "ExecutableStepDTO",
|
||||
"stepOrder": 1,
|
||||
"stepTypeId": 1,
|
||||
"childStepId": None,
|
||||
"endConditionId": 2,
|
||||
"endConditionValue": 300,
|
||||
"targetTypeId": 1,
|
||||
"targetValueOne": 10.0,
|
||||
"targetValueTwo": 12.0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def test_ai_modification():
|
||||
print("\n--- Testing AI Workout Modifier ---")
|
||||
|
||||
# 1. Setup Environment
|
||||
root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))
|
||||
env = EnvManager(root_dir)
|
||||
env.load_service_env("gemini")
|
||||
|
||||
api_key = os.getenv("GEMINI_API_KEY")
|
||||
if not api_key:
|
||||
print("SKIP: GEMINI_API_KEY not found in environment.")
|
||||
return
|
||||
|
||||
# 2. Prepare Data
|
||||
manager = WorkoutManager()
|
||||
original_workout = load_sample_workout()
|
||||
|
||||
# 3. specific instruction
|
||||
prompt = "Add a 10 minute cooldown at the end."
|
||||
print(f"Prompt: {prompt}")
|
||||
|
||||
# 4. Execute AI Modification
|
||||
try:
|
||||
modified_workout = manager.generate_workout_json(prompt, existing_workout=original_workout)
|
||||
|
||||
# 5. Verify Structure (Schema Validation)
|
||||
errors = manager.validate_workout_json(modified_workout)
|
||||
if errors:
|
||||
print(f"FAIL: Schema Validation Errors: {json.dumps(errors, indent=2)}")
|
||||
print(f"Invalid Workout JSON: {json.dumps(modified_workout, indent=2)}")
|
||||
return
|
||||
|
||||
# 6. Verify Content Logic
|
||||
segments = modified_workout.get("workoutSegments", [])
|
||||
if not segments:
|
||||
print("FAIL: No segments found in modified workout.")
|
||||
return
|
||||
|
||||
steps = segments[0].get("workoutSteps", [])
|
||||
last_step = steps[-1] if steps else None
|
||||
|
||||
# Check if last step looks like a cooldown
|
||||
# stepTypeId 4 = Cooldown (usually, or we check description/type)
|
||||
is_cooldown = last_step and (last_step.get("stepTypeId") == 4 or "cool" in str(last_step).lower())
|
||||
|
||||
if is_cooldown:
|
||||
print("PASS: Cooldown added successfully.")
|
||||
print(f"Modified Steps Count: {len(steps)}")
|
||||
else:
|
||||
print("WARN: Could not strictly verify cooldown type, but schema is valid.")
|
||||
print(f"Last Step: {json.dumps(last_step, indent=2)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"FAIL: Exception during AI processing: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_ai_modification()
|
||||
Reference in New Issue
Block a user