## Implemented
### DB-Schema (Migrations)
- Migration 013: training_parameters table (16 standard parameters)
- Migration 014: training_types.profile + activity_log.evaluation columns
- Performance metric calculations (avg_hr_percent, kcal_per_km)
### Backend - Rule Engine
- RuleEvaluator: Generic rule evaluation with 9 operators
- gte, lte, gt, lt, eq, neq, between, in, not_in
- Weighted scoring system
- Pass strategies: all_must_pass, weighted_score, at_least_n
- IntensityZoneEvaluator: HR zone analysis
- TrainingEffectsEvaluator: Abilities development
### Backend - Master Evaluator
- TrainingProfileEvaluator: 7-dimensional evaluation
1. Minimum Requirements (Quality Gates)
2. Intensity Zones (HR zones)
3. Training Effects (Abilities)
4. Periodization (Frequency & Recovery)
5. Performance Indicators (KPIs)
6. Safety (Warnings)
7. AI Context (simplified for MVP)
- evaluation_helper.py: Utilities for loading + saving
- routers/evaluation.py: API endpoints
- POST /api/evaluation/activity/{id}
- POST /api/evaluation/batch
- GET /api/evaluation/parameters
### Integration
- main.py: Router registration
## TODO (Phase 1.2)
- Auto-evaluation on activity INSERT/UPDATE
- Admin-UI for profile editing
- User-UI for results display
## Testing
- ✅ Syntax checks passed
- 🔲 Runtime testing pending (after auto-evaluation)
Part of Issue #15 - Training Type Profiles System
147 lines
3.7 KiB
Python
147 lines
3.7 KiB
Python
"""
|
|
Evaluation Endpoints - Training Type Profiles
|
|
Endpoints for activity evaluation and re-evaluation.
|
|
|
|
Issue: #15
|
|
Date: 2026-03-23
|
|
"""
|
|
import logging
|
|
from typing import Optional
|
|
from fastapi import APIRouter, HTTPException, Depends
|
|
|
|
from db import get_db, get_cursor, r2d
|
|
from auth import require_auth, require_admin
|
|
from evaluation_helper import (
|
|
evaluate_and_save_activity,
|
|
batch_evaluate_activities,
|
|
load_parameters_registry
|
|
)
|
|
|
|
router = APIRouter(prefix="/api/evaluation", tags=["evaluation"])
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@router.get("/parameters")
|
|
def list_parameters(session: dict = Depends(require_auth)):
|
|
"""
|
|
List all available training parameters.
|
|
"""
|
|
with get_db() as conn:
|
|
cur = get_cursor(conn)
|
|
parameters = load_parameters_registry(cur)
|
|
|
|
return {
|
|
"parameters": list(parameters.values()),
|
|
"count": len(parameters)
|
|
}
|
|
|
|
|
|
@router.post("/activity/{activity_id}")
|
|
def evaluate_activity(
|
|
activity_id: str,
|
|
session: dict = Depends(require_auth)
|
|
):
|
|
"""
|
|
Evaluates or re-evaluates a single activity.
|
|
|
|
Returns the evaluation result.
|
|
"""
|
|
profile_id = session['profile_id']
|
|
|
|
with get_db() as conn:
|
|
cur = get_cursor(conn)
|
|
|
|
# Load activity
|
|
cur.execute("""
|
|
SELECT id, profile_id, date, training_type_id, duration_min,
|
|
hr_avg, hr_max, distance_km, kcal_active, kcal_resting,
|
|
rpe, pace_min_per_km, cadence, elevation_gain
|
|
FROM activity_log
|
|
WHERE id = %s AND profile_id = %s
|
|
""", (activity_id, profile_id))
|
|
|
|
activity = cur.fetchone()
|
|
if not activity:
|
|
raise HTTPException(404, "Activity not found")
|
|
|
|
activity_dict = dict(activity)
|
|
|
|
# Evaluate
|
|
result = evaluate_and_save_activity(
|
|
cur,
|
|
activity_dict["id"],
|
|
activity_dict,
|
|
activity_dict["training_type_id"],
|
|
profile_id
|
|
)
|
|
|
|
if not result:
|
|
return {
|
|
"message": "No profile configured for this training type",
|
|
"evaluation": None
|
|
}
|
|
|
|
return {
|
|
"message": "Activity evaluated",
|
|
"evaluation": result
|
|
}
|
|
|
|
|
|
@router.post("/batch")
|
|
def batch_evaluate(
|
|
limit: Optional[int] = None,
|
|
session: dict = Depends(require_auth)
|
|
):
|
|
"""
|
|
Re-evaluates all activities for the current user.
|
|
|
|
Optional limit parameter for testing.
|
|
"""
|
|
profile_id = session['profile_id']
|
|
|
|
with get_db() as conn:
|
|
cur = get_cursor(conn)
|
|
stats = batch_evaluate_activities(cur, profile_id, limit)
|
|
|
|
return {
|
|
"message": "Batch evaluation completed",
|
|
"stats": stats
|
|
}
|
|
|
|
|
|
@router.post("/batch/all")
|
|
def batch_evaluate_all(session: dict = Depends(require_admin)):
|
|
"""
|
|
Admin-only: Re-evaluates all activities for all users.
|
|
|
|
Use with caution on large databases!
|
|
"""
|
|
with get_db() as conn:
|
|
cur = get_cursor(conn)
|
|
|
|
# Get all profiles
|
|
cur.execute("SELECT id FROM profiles")
|
|
profiles = cur.fetchall()
|
|
|
|
total_stats = {
|
|
"profiles": len(profiles),
|
|
"total": 0,
|
|
"evaluated": 0,
|
|
"skipped": 0,
|
|
"errors": 0
|
|
}
|
|
|
|
for profile in profiles:
|
|
profile_id = profile['id']
|
|
stats = batch_evaluate_activities(cur, profile_id)
|
|
|
|
total_stats["total"] += stats["total"]
|
|
total_stats["evaluated"] += stats["evaluated"]
|
|
total_stats["skipped"] += stats["skipped"]
|
|
total_stats["errors"] += stats["errors"]
|
|
|
|
return {
|
|
"message": "Batch evaluation for all users completed",
|
|
"stats": total_stats
|
|
}
|