Time Series
Overview
The application implements a time series system for tracking runner performance metrics over time. This architecture enables historical data preservation, period-appropriate calculations, and automatic performance metric updates.
Data Structure
All time series data follows a consistent structure (see Database Schema):
{
"timestamp": ISODate("2025-01-15T00:00:00.000Z"),
"value": { /* metric-specific data */ },
"source": "session_upload", // How the value was obtained
"notes": "Optional description"
}
Applied to:
- Heart Rate Zones (
heart_rate_zones_history) - Training zone evolution - Personal Bests (
pb_history) - Best times across multiple distances - Performance Metrics (
performance_metrics_time_series) - Fitness marker progression
Sequential Processing Workflow
1. File Upload & Sorting
User uploads FIT files (single or ZIP)
↓
Extract all FIT files from ZIP archives
↓
Extract session dates from each FIT file
↓
Sort files chronologically (oldest first)
↓
Process sequentially
Implementation: app/file_processor.py:process_uploaded_files()
2. Session Processing with Time Series
For each session (oldest to newest):
1. Get session date from FIT file
↓
2. Query time series for values at that date
- Heart rate zones active at session date
- Personal bests current at session date
- Critical speed valid at session date
↓
3. Calculate statistics using period-appropriate values
↓
4. Detect if any personal bests were achieved
↓
5. If PB detected → Update PB time series
↓
6. If PB updated → Recalculate critical speed
↓
7. Move to next session (repeat)
3. Automatic PB Detection & Updates
Detection Logic:
# For each tracked distance in session
current_pb = get_value_at_date(pb_history, session_date)
session_time = extract_segment_time(session, distance)
if session_time < current_pb:
# New PB detected!
add_value(pb_history,
value={'distance': distance, 'seconds': session_time},
timestamp=session_date,
source='session_upload')
4. Critical Speed Recalculation
When PBs are updated, Critical Speed is automatically recalculated using the updated values at that point in time.
5. Historical Upload Cascade
When a session older than 7 days is uploaded:
Detect historical upload (session_date < now - 7 days)
↓
Check if newer sessions exist
↓
If yes → Trigger cascade recalculation
↓
Reprocess all sessions after the historical date
↓
Ensures statistics use updated PBs/CS
Key Components
| Component | Responsibility | Location |
|---|---|---|
| Time Series Utils | Generic time series operations | app/utils/time_series.py |
| Runner Service | Update HR zones, PBs, CS | app/services/runner_service.py |
| File Processor | Sequential session processing | app/file_processor.py |
| Statistics Processor | Period-aware calculations | app/statistics_processor.py |
| REST API | Time series CRUD operations | app/routes.py |
Time Series Utility Functions
Located in app/utils/time_series.py:
get_value_at_date(time_series, date)- Binary search to find value active at specific dateget_latest_value(time_series)- Get most recent valueadd_value(time_series, value, timestamp, source)- Add new entry, maintaining chronological orderget_value_history(time_series, start_date, end_date)- Query date range
REST API Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
/api/runners/<id>/heart-rate-zones | GET | Get HR zones history |
/api/runners/<id>/heart-rate-zones | POST | Update HR zones |
/api/runners/<id>/personal-bests | GET | Get PB history |
/api/runners/<id>/personal-bests | POST | Update PB |
/api/runners/<id>/critical-speed | GET | Get CS history |
/api/runners/<id>/critical-speed | POST | Update CS |
/api/defaults/schedule | GET | Get global training schedule defaults |
Benefits Achieved
- Historical Preservation - No data loss when values change
- Period-Appropriate Calculations - Sessions use values valid at their date
- Automatic Maintenance - PBs and CS update without manual intervention
- Chronological Consistency - Upload order doesn't affect results
- Retroactive Corrections - Historical uploads trigger necessary recalculations
Migration
Time series was implemented in three phases:
- Migration 003 - Heart rate zones → time series
- Migration 004 - Personal bests → time series
- Migration 005 - Critical speed → time series
All existing data was preserved during migration. Legacy fields remain in the schema for backward compatibility.
For complete schema details, see Database Schema.