This commit is contained in:
2025-09-25 09:04:37 -07:00
parent e06683b875
commit 415126770b
13 changed files with 103 additions and 28 deletions

View File

@@ -6,6 +6,8 @@ CLI Interface - Simple command line interface for the cycling analyzer
import asyncio
import logging
from pathlib import Path
import argparse
import sys
from config import Config, load_config, create_sample_config
from core_app import CyclingAnalyzerApp
@@ -17,11 +19,8 @@ class CLI:
def __init__(self):
self.app = None
async def run(self):
async def run(self, args):
"""Main CLI loop"""
print("Cycling Workout Analyzer")
print("=" * 40)
# Setup configuration
try:
config = self._setup_config()
@@ -33,11 +32,13 @@ class CLI:
# Initialize app
await self.app.initialize()
# Show initial status
self._show_status()
# Main loop
await self._main_loop()
if args.command:
await self._handle_command(args)
else:
# Show initial status
await self._show_status()
# Main loop
await self._main_loop()
except KeyboardInterrupt:
print("\nGoodbye!")
@@ -67,10 +68,10 @@ class CLI:
return config
def _show_status(self):
async def _show_status(self):
"""Show application status"""
print(f"\nStatus:")
print(f"- Available tools: {len(self.app.list_available_tools())}")
print(f"- Available tools: {len(await self.app.list_available_tools())}")
print(f"- Available templates: {len(self.app.list_templates())}")
print(f"- Cached data keys: {list(self.app.get_cached_data().keys())}")
@@ -100,7 +101,7 @@ class CLI:
elif choice == "3":
await self._enhanced_analysis()
elif choice == "4":
self._list_tools()
await self._list_tools()
elif choice == "5":
self._list_templates()
elif choice == "6":
@@ -184,9 +185,9 @@ class CLI:
print("="*50)
print(result)
def _list_tools(self):
async def _list_tools(self):
"""List available tools"""
tools = self.app.list_available_tools()
tools = await self.app.list_available_tools()
if tools:
self.app.mcp_client.print_tools()
else:
@@ -247,10 +248,21 @@ Weekly Structure:
f.write(default_rules)
return default_rules
async def _handle_command(self, args):
"""Handle non-interactive commands"""
if args.command == "analyze_last":
await self._analyze_last_workout()
else:
print(f"Unknown command: {args.command}")
async def main():
"""CLI entry point"""
parser = argparse.ArgumentParser(description="Cycling Workout Analyzer")
parser.add_argument('command', nargs='?', help="Command to execute (e.g., analyze_last)")
args = parser.parse_args()
cli = CLI()
await cli.run()
await cli.run(args)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -70,7 +70,7 @@ class CyclingAnalyzerApp:
act for act in activities
if "cycling" in act.get("activityType", {}).get("typeKey", "").lower()
]
return max(cycling_activities, key=lambda x: x.get("start_time", "")) if cycling_activities else None
return max(cycling_activities, key=lambda x: x.get("startTimeGmt", 0)) if cycling_activities else None
# Core functionality methods
@@ -80,13 +80,13 @@ class CyclingAnalyzerApp:
# Prepare context data
context = {
"user_profile": self.cache_manager.get("user_profile", {}),
"recent_activities": self.cache_manager.get("recent_activities", []),
"last_cycling_details": self.cache_manager.get("last_cycling_details", {}),
"user_info": self.cache_manager.get("user_profile", {}),
"activity_summary": self.cache_manager.get("last_cycling_details", {}),
**kwargs
}
# Load and render template
logger.info(f"Rendering template {template_name} with context keys: {list(context.keys())}")
prompt = self.template_engine.render(template_name, **context)
# Call LLM
@@ -122,9 +122,9 @@ class CyclingAnalyzerApp:
# Utility methods
def list_available_tools(self) -> list:
async def list_available_tools(self) -> list:
"""Get list of available MCP tools"""
return self.mcp_client.list_tools()
return await self.mcp_client.list_tools()
def list_templates(self) -> list:
"""Get list of available templates"""
@@ -145,13 +145,13 @@ async def main():
await app.initialize()
# Example usage
print("Available tools:", len(app.list_available_tools()))
print("Available tools:", len(await app.list_available_tools()))
print("Available templates:", len(app.list_templates()))
# Run analysis
analysis = await app.analyze_workout("analyze_last_workout",
training_rules="Sample rules")
print("Analysis:", analysis[:200] + "...")
# analysis = await app.analyze_workout("analyze_last_workout",
# training_rules="Sample rules")
# print("Analysis:", analysis[:200] + "...")
except Exception as e:
logger.error(f"Application error: {e}")

17
rules.yaml Normal file
View File

@@ -0,0 +1,17 @@
Training Goals:
- Improve FTP (Functional Threshold Power)
- Build endurance for long rides
- Maintain consistent training
Power Zones (adjust based on your FTP):
- Zone 1 (Active Recovery): < 55% FTP
- Zone 2 (Endurance): 56-75% FTP
- Zone 3 (Tempo): 76-90% FTP
- Zone 4 (Lactate Threshold): 91-105% FTP
- Zone 5 (VO2 Max): 106-120% FTP
Weekly Structure:
- 70-80% easy/moderate intensity
- 20-30% high intensity
- At least 1 rest day per week

View File

@@ -122,11 +122,13 @@ class TemplateEngine:
try:
section_content = self.load_template(section_file)
# Render section with same kwargs
section_rendered = section_content.format(**kwargs)
# Recursively render the section content
section_rendered = self.render(section_file, **kwargs)
content = content.replace(placeholder, section_rendered)
except (FileNotFoundError, KeyError) as e:
except (FileNotFoundError, KeyError, ValueError) as e:
logger.warning(f"Could not process section {section_name}: {e}")
# Leave placeholder as-is if section can't be loaded
# Replace with empty string if section fails
content = content.replace(placeholder, "")
return content

View File

@@ -0,0 +1,7 @@
Please provide:
1. Overall assessment of the workout
2. How well it aligns with my rules and goals
3. Areas for improvement
4. Specific feedback on power, heart rate, duration, and intensity
5. Recovery recommendations
6. Comparison with typical performance metrics

View File

@@ -0,0 +1,2 @@
ACTIVITY SUMMARY:
Activity ID: {activity_summary[activityId]}

View File

@@ -0,0 +1,2 @@
My training rules and goals:
{training_rules}

View File

@@ -0,0 +1,2 @@
USER INFO:
User ID: {user_info[id]}

View File

@@ -0,0 +1,3 @@
You are an expert cycling coach with access to comprehensive Garmin Connect data.
You analyze cycling workouts, provide performance insights, and give actionable training recommendations.
Use the available tools to gather detailed workout data and provide comprehensive analysis.

View File

@@ -0,0 +1,2 @@
You are an expert cycling coach. Perform comprehensive analysis using the provided data.
Do not use any tools - all relevant data is included in the prompt.

View File

@@ -0,0 +1,11 @@
Analyze my most recent cycling workout using the provided data.
{activity_summary_section}
{user_info_section}
{training_rules_section}
{assessment_points}
Focus on the provided activity details for your analysis.

View File

@@ -0,0 +1,5 @@
Perform enhanced {analysis_type} analysis using all available data and tools.
Available cached data: {cached_data}
Use MCP tools as needed to gather additional data for comprehensive analysis.

View File

@@ -0,0 +1,10 @@
Please suggest my next cycling workout based on my recent training history.
{training_rules_section}
Please provide:
1. Analysis of my recent training pattern
2. Identified gaps or imbalances in my training
3. Specific workout recommendation for my next session
4. Target zones (power, heart rate, duration)
5. Rationale for the recommendation based on recent performance