| """Synthetic Strava-style activities for demos and local testing. |
| |
| Each demo profile is designed to trip a *different* signal in the analytics |
| engine, so the demo dropdown doubles as a tour of what the engine detects: |
| |
| excelling -> increasing_consistency + improving pace, safe build |
| stagnating -> flat mileage & pace -> pace_trend "plateauing" |
| returning_after_break -> ~2-week gap at the end -> "returning_after_break" |
| |
| Dates are relative to "now" and built fresh on every call (via the builder |
| functions below), so the runs always land in the right rolling-week buckets no |
| matter how long the app has been running. |
| """ |
|
|
| from datetime import datetime, timedelta |
|
|
|
|
| def _iso(days_ago: float, hour: int = 8) -> str: |
| d = (datetime.now() - timedelta(days=days_ago)).replace( |
| hour=hour, minute=0, second=0, microsecond=0 |
| ) |
| return d.isoformat() |
|
|
|
|
| def _excelling() -> list[dict]: |
| """Consistent runner on a healthy upward trend over ~8 weeks. |
| |
| Mileage grows ~8% week-over-week (under the 10% spike threshold), pace |
| steadily improves, and run count ticks up this week (3 -> 4). |
| """ |
| return [ |
| |
| {"type": "Run", "distance": 5000, "moving_time": 1475, "start_date": _iso(1)}, |
| {"type": "Run", "distance": 5500, "moving_time": 1620, "start_date": _iso(3)}, |
| {"type": "Run", "distance": 4000, "moving_time": 1180, "start_date": _iso(5)}, |
| {"type": "Run", "distance": 5000, "moving_time": 1475, "start_date": _iso(6)}, |
| |
| {"type": "Run", "distance": 6000, "moving_time": 1830, "start_date": _iso(8)}, |
| {"type": "Run", "distance": 6000, "moving_time": 1830, "start_date": _iso(10)}, |
| {"type": "Run", "distance": 6000, "moving_time": 1830, "start_date": _iso(12)}, |
| |
| {"type": "Run", "distance": 5500, "moving_time": 1733, "start_date": _iso(15)}, |
| {"type": "Run", "distance": 5500, "moving_time": 1733, "start_date": _iso(17)}, |
| {"type": "Run", "distance": 5500, "moving_time": 1733, "start_date": _iso(19)}, |
| |
| {"type": "Run", "distance": 5000, "moving_time": 1625, "start_date": _iso(22)}, |
| {"type": "Run", "distance": 5000, "moving_time": 1625, "start_date": _iso(24)}, |
| {"type": "Run", "distance": 5000, "moving_time": 1625, "start_date": _iso(26)}, |
| |
| {"type": "Run", "distance": 6500, "moving_time": 2178, "start_date": _iso(29)}, |
| {"type": "Run", "distance": 6500, "moving_time": 2178, "start_date": _iso(32)}, |
| |
| {"type": "Run", "distance": 6000, "moving_time": 2040, "start_date": _iso(36)}, |
| {"type": "Run", "distance": 6000, "moving_time": 2040, "start_date": _iso(39)}, |
| |
| {"type": "Run", "distance": 5000, "moving_time": 1725, "start_date": _iso(43)}, |
| {"type": "Run", "distance": 5000, "moving_time": 1725, "start_date": _iso(46)}, |
| |
| {"type": "Run", "distance": 8000, "moving_time": 2800, "start_date": _iso(51)}, |
| |
| {"type": "Run", "distance": 40, "moving_time": 9, "start_date": _iso(2)}, |
| {"type": "Ride", "distance": 24000, "moving_time": 3600, "start_date": _iso(4)}, |
| ] |
|
|
|
|
| def _stagnating() -> list[dict]: |
| """Reliable but stuck: same mileage and pace every week for a month. |
| |
| Trends: mileage change ~0%, pace flat -> "plateauing", no growth signals. |
| The story the coach should tell is 'add stimulus to break the plateau'. |
| """ |
| runs = [] |
| |
| for week in range(8): |
| base = week * 7 |
| for offset in (1, 3, 5): |
| runs.append( |
| {"type": "Run", "distance": 5000, "moving_time": 1600, |
| "start_date": _iso(base + offset)} |
| ) |
| |
| runs.append({"type": "Ride", "distance": 18000, "moving_time": 3000, "start_date": _iso(4)}) |
| return runs |
|
|
|
|
| def _returning_after_break() -> list[dict]: |
| """Trained well, then took ~2 weeks off — last run was 16 days ago. |
| |
| days_since_last_run > 10 -> "returning_after_break". This and last week are |
| empty, so the chart shows the drop-off and the coach should ease them back in. |
| """ |
| return [ |
| |
| |
| {"type": "Run", "distance": 5000, "moving_time": 1600, "start_date": _iso(16)}, |
| {"type": "Run", "distance": 6000, "moving_time": 1980, "start_date": _iso(18)}, |
| {"type": "Run", "distance": 5000, "moving_time": 1650, "start_date": _iso(20)}, |
| |
| {"type": "Run", "distance": 6000, "moving_time": 2040, "start_date": _iso(23)}, |
| {"type": "Run", "distance": 7000, "moving_time": 2380, "start_date": _iso(25)}, |
| {"type": "Run", "distance": 5000, "moving_time": 1700, "start_date": _iso(27)}, |
| |
| {"type": "Run", "distance": 6000, "moving_time": 2010, "start_date": _iso(29)}, |
| {"type": "Run", "distance": 6000, "moving_time": 2010, "start_date": _iso(31)}, |
| {"type": "Run", "distance": 7000, "moving_time": 2345, "start_date": _iso(33)}, |
| |
| {"type": "Run", "distance": 5000, "moving_time": 1700, "start_date": _iso(36)}, |
| {"type": "Run", "distance": 6000, "moving_time": 2040, "start_date": _iso(38)}, |
| |
| {"type": "Run", "distance": 6000, "moving_time": 2010, "start_date": _iso(43)}, |
| {"type": "Run", "distance": 6000, "moving_time": 2010, "start_date": _iso(45)}, |
| {"type": "Run", "distance": 5000, "moving_time": 1700, "start_date": _iso(47)}, |
| |
| {"type": "Run", "distance": 5000, "moving_time": 1725, "start_date": _iso(51)}, |
| {"type": "Run", "distance": 6000, "moving_time": 2070, "start_date": _iso(54)}, |
| |
| {"type": "Run", "distance": 25, "moving_time": 6, "start_date": _iso(17)}, |
| ] |
|
|
|
|
| _DEMOS = { |
| "excelling": _excelling, |
| "stagnating": _stagnating, |
| "returning_after_break": _returning_after_break, |
| } |
|
|
| |
| DEMO_GROUPS = tuple(_DEMOS) |
|
|
|
|
| def demo_activities(demo: str = "excelling") -> list[dict]: |
| """Return a fresh synthetic activity list for the chosen demo profile.""" |
| builder = _DEMOS.get(demo, _excelling) |
| return builder() |
|
|