Add segments, YTD stats, route matching fixes, body battery layout, pace fix
- Segments page: new /segments route with auto-generate (1km splits, turn detection, hill detection), manual segment creation, per-segment performance times across matched activities; fixed auth on existing segment endpoints - YTD distance: new /activities/stats/ytd endpoint; Dashboard replaces 'Total distance' with 'Running this year' + 'Cycling this year'; Activities page shows YTD stats row - Weekly chart click: clicking a Dashboard bar navigates to Activities filtered to that week; Activities reads from/to query params with dismissable chip - Route matching: add ±2.5% distance gate + 3% relative DTW threshold (was flat 80m); tighten candidate pre-filter from 80/120% to 95/105% - Body battery layout: HR chart and body battery now side-by-side at same height on large screens instead of stacked full-width - Pace display fix: MetricTimeline clamps GPS speed outliers before computing Y-axis domain; tick formatter guards against v<=0 or v>25 m/s Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -75,6 +75,30 @@ class LapOut(BaseModel):
|
||||
from_attributes = True
|
||||
|
||||
|
||||
@router.get("/stats/ytd")
|
||||
async def ytd_stats(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Return year-to-date distance totals grouped by sport type."""
|
||||
from datetime import date, timezone
|
||||
year_start = datetime(date.today().year, 1, 1, tzinfo=timezone.utc)
|
||||
result = await db.execute(
|
||||
select(Activity.sport_type, func.sum(Activity.distance_m).label("total_m"))
|
||||
.where(Activity.user_id == current_user.id, Activity.start_time >= year_start)
|
||||
.group_by(Activity.sport_type)
|
||||
)
|
||||
rows = result.all()
|
||||
totals = {r.sport_type: (r.total_m or 0) / 1000 for r in rows}
|
||||
return {
|
||||
"running_km": round(totals.get("running", 0), 2),
|
||||
"cycling_km": round(totals.get("cycling", 0), 2),
|
||||
"hiking_km": round(totals.get("hiking", 0), 2),
|
||||
"walking_km": round(totals.get("walking", 0), 2),
|
||||
"total_km": round(sum(totals.values()), 2),
|
||||
}
|
||||
|
||||
|
||||
@router.get("/", response_model=List[ActivitySummary])
|
||||
async def list_activities(
|
||||
page: int = Query(1, ge=1),
|
||||
|
||||
Reference in New Issue
Block a user