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 asyncio
import logging import logging
from pathlib import Path from pathlib import Path
import argparse
import sys
from config import Config, load_config, create_sample_config from config import Config, load_config, create_sample_config
from core_app import CyclingAnalyzerApp from core_app import CyclingAnalyzerApp
@@ -17,11 +19,8 @@ class CLI:
def __init__(self): def __init__(self):
self.app = None self.app = None
async def run(self): async def run(self, args):
"""Main CLI loop""" """Main CLI loop"""
print("Cycling Workout Analyzer")
print("=" * 40)
# Setup configuration # Setup configuration
try: try:
config = self._setup_config() config = self._setup_config()
@@ -33,9 +32,11 @@ class CLI:
# Initialize app # Initialize app
await self.app.initialize() await self.app.initialize()
if args.command:
await self._handle_command(args)
else:
# Show initial status # Show initial status
self._show_status() await self._show_status()
# Main loop # Main loop
await self._main_loop() await self._main_loop()
@@ -67,10 +68,10 @@ class CLI:
return config return config
def _show_status(self): async def _show_status(self):
"""Show application status""" """Show application status"""
print(f"\nStatus:") 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"- Available templates: {len(self.app.list_templates())}")
print(f"- Cached data keys: {list(self.app.get_cached_data().keys())}") print(f"- Cached data keys: {list(self.app.get_cached_data().keys())}")
@@ -100,7 +101,7 @@ class CLI:
elif choice == "3": elif choice == "3":
await self._enhanced_analysis() await self._enhanced_analysis()
elif choice == "4": elif choice == "4":
self._list_tools() await self._list_tools()
elif choice == "5": elif choice == "5":
self._list_templates() self._list_templates()
elif choice == "6": elif choice == "6":
@@ -184,9 +185,9 @@ class CLI:
print("="*50) print("="*50)
print(result) print(result)
def _list_tools(self): async def _list_tools(self):
"""List available tools""" """List available tools"""
tools = self.app.list_available_tools() tools = await self.app.list_available_tools()
if tools: if tools:
self.app.mcp_client.print_tools() self.app.mcp_client.print_tools()
else: else:
@@ -247,10 +248,21 @@ Weekly Structure:
f.write(default_rules) f.write(default_rules)
return 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(): async def main():
"""CLI entry point""" """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() cli = CLI()
await cli.run() await cli.run(args)
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(main()) asyncio.run(main())

View File

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