Latest state on privte laptop

This commit is contained in:
Moritz Graf
2026-01-02 17:40:18 +01:00
parent 715da2a816
commit 408cfa87d1
10 changed files with 701 additions and 267 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
import os
from typing import Any, Dict
from typing import Any, Dict, List
from dotenv import load_dotenv, set_key
@@ -34,7 +34,7 @@ class EnvManager:
# Reload after setting
self.load_service_env(service, override=True)
def get_status(self, service: str, required_keys: list[str]) -> Dict[str, Any]:
def get_status(self, service: str, required_keys: List[str]) -> Dict[str, Any]:
"""Check if required keys are set for a service."""
# Reload just in case
self.load_service_env(service)
+12 -1
View File
@@ -89,8 +89,19 @@ class RecommendationEngine:
Validation Rules:
- SportTypes: RUNNING=1, CYCLING=2
- StepTypes: WARMUP=1, COOLDOWN=2, INTERVAL=3, RECOVERY=4, REST=5, REPEAT=6
- EndCondition: DISTANCE=1, TIME=2, LAP_BUTTON=7
- EndCondition: DISTANCE=1, TIME=2, LAP_BUTTON=7, ITERATIONS=7
- TargetType: NO_TARGET=1, HEART_RATE=2, PACE=4 (Speed)
CRITICAL SCHEMA RULE:
Steps MUST use nested objects for types. Do NOT use flat IDs.
Example Step:
{
"type": "ExecutableStepDTO",
"stepOrder": 1,
"stepType": { "stepTypeId": 1, "stepTypeKey": "warmup" },
"endCondition": { "conditionTypeId": 2, "conditionTypeKey": "time" },
"endConditionValue": 600
}
"""
user_prompt = f"User Request: {prompt}"
+102
View File
@@ -0,0 +1,102 @@
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()