Activity Calendar
The monthly activity grid — colored run dots, week totals, day detail, and training status rings. See Predictive Injury Calendar for what the rings mean.
For the full guide to training status rings and the 14-day injury forecast, start with Predictive Injury Calendar.

Source: src/app/(app)/calendar/page.tsx
What You See
A monthly calendar grid. Days when you ran have colored dots. You can tap a day to see a list of activities for that day, including the type of run, distance, and duration. Arrow buttons let you move between months.
How the Numbers Work
PIRX loads all your synced activities for the visible month and groups them by day. Each activity is placed on the day it happened in your local time zone, so a run at 7 PM always shows on the correct day even if the server stores it in a different time zone.
The colored ring around each day cell is the training status:
- Green — Good to train.
- Yellow — Reduce intensity. Recent load or recovery markers are starting to drift outside normal range.
- Red — Rest recommended. Injury risk is elevated or readiness has dropped enough to warrant a full rest day.
- No ring — Either green or the day is beyond our forecast horizon.
Past days and today are based on measured data: actual training load, ACWR, weekly load variance, sleep, HRV, and resting HR through the day. Future days inside a 14-day horizon are forecast by simulating "if you keep training your recent pattern" — they show as a slightly dimmer ring so they're visually distinct from measured days. Beyond day 14, no forecast is shown (the day appears with no ring and the day-detail card explains "No forecast yet").
The day-detail card only shows the yellow/red explanation banner for measured days. Forecast days never show actionable banners because the reason (e.g., "ACWR climbing") is a what-if from the simulator, not something you should act on today.
Step by Step
The backend queries the activities table for all records in the visible month's date range. Each activity has a UTC start time, distance in meters, duration in seconds, and a classification label (Easy, Tempo, Long, Interval, Race) set during the cleaning pipeline. The rule-based classifier uses HR as a percentage of max, pace relative to thresholds, distance, and activity name keywords to assign the label. The browser converts UTC timestamps to your local timezone, groups activities by day, and draws colored dots. Tapping a day filters the already-loaded list client-side.
For the training-status ring:
- Today is computed live in the request: PIRX pulls the latest feature snapshot (ACWR, weekly load standard deviation, days-since-activity), runs the injury-risk model, computes readiness from recent HRV / resting HR / sleep, and classifies the day.
- Past days read their persisted feature snapshot and stored injury assessment from the database — no recompute.
- Future days (up to 14 days out) read from a
projected_training_statustable. A daily Celery job at 07:30 UTC walks each whitelisted user, builds a 7-day daily-load pattern from recent activities, and simulates forward day-by-day: it advances the acute (7d) and chronic (28d) rolling load windows, recalculates ACWR, increments recency counters, then re-runs the injury heuristic, readiness heuristic, and the sameclassify_training_statusfunction used for today. The output is upserted as one row per projection date withprojection_method="simulated_maintain". - Days past the 14-day horizon have no row and the frontend renders them as
status="unknown".
The simulator does not look at race cards or periodization plans — it strictly projects "keep doing what you're doing." Weekly load standard deviation is frozen from today's value, since the simulator has no information about future planned workouts.
Predictive Injury Calendar
How PIRX shows injury risk and training readiness on your Calendar — today's status, past days, and a 14-day forecast of when to push and when to back off.
Rankings / Leaderboard
The Rankings page lets you see how your projection compares to other PIRX runners for the same race distance.