# Run analysis DSL Write queries in **Stats**, use **Ctrl+Space** for completion and **Ctrl/Cmd+Enter** to execute. **Format** produces a canonical query. Live validation uses the same parser as execution. Comments occupy their own lines starting with `#`. ## Largest gaps between runs ```text runs during all time summarize gap_since_previous_run, distance by run sort by gap_since_previous_run desc take 10 compare none ``` Each gap is actual elapsed start-to-start time, expressed in days. Its predecessor comes from your complete running history, even if it falls outside the period or fails the query's filters. The first recorded run has a missing gap. Each result links to the preceding run; this cannot reveal unrecorded activities. ## Statistical analysis ```text runs during last 24 months where distance >= 5 km summarize count(), median(pace), p90(heart_rate), sum(distance) by weekday having count() >= 5 runs rank by median(pace) asc limit 4 compare none plot speed vs heart_rate ``` Functions: `sum`, `mean`, `weighted_mean`, `median`, `min`, `max`, `stddev`, `count`, `p10`, `p25`, `p75`, `p90`, `p95`, `p99`. Use `count()` for all runs or `count(power)` for runs with that metric. Percentiles use linear interpolation at `(n-1)*p`; standard deviation is population standard deviation. `mean(pace)` gives each run equal weight. `weighted_mean(pace)` weights by recorded distance. Other weighted means use timer duration. Bare `pace` is total timer time / distance; bare heart rate and running dynamics are weighted by timer time. Bare distance, duration, ascent, calories and count are totals. Other bare metrics are unweighted means. Calculated-field bare summaries are means. Missing values are excluded, not replaced by zero. Empty sums and means remain missing; counts are zero. Nonpositive recorded pace and sensor values are missing. Each summary card shows usable-run coverage and its definition. ## Metrics and units | Metric | Input unit(s) | |---|---| | `distance`, `distance_change`, `rolling_7d_distance`, `rolling_28d_distance` | `km`, `m` | | `duration` | `h`, `min`, `s` | | `pace` | `/km`, such as `5:30 /km` or `330 /km` | | `pace_change` | `sec/km`, signed; negative is faster | | `heart_rate`, `heart_rate_change`, `max_heart_rate` | `bpm` | | `elevation`, `stride_length` | `m` | | `cadence` | `spm` | | `power` | `W` | | `ground_contact` | `ms` | | `vertical_oscillation` | `cm` | | `speed` | `km/h`, `m/s` | | `gap_since_previous_run` | `days`, `h`, `min` | | `run_day_streak` | `days` | | `count`, `rolling_7d_count`, `rolling_28d_count` | `runs` | | `elevation_per_km` | `m/km` | | `calories` | `kcal` | | `training_effect` | `score` | Changes compare with the immediately preceding recorded run before filtering. Rolling 7/28-day metrics include this run and earlier runs less than 168/672 hours old; the exact boundary is excluded. Streaks count consecutive local calendar days with a run. Multiple runs on one day don't lengthen the streak. Pace standard deviations and sums use numeric `sec/km`, not pace formatting. Garmin calories and aerobic training effect remain missing when not recorded. ## Calculated fields ```text runs during last 12 weeks let efficiency = speed / heart_rate let pace_ratio = pace / pace where efficiency > 0.07 (km/h/bpm) summarize mean(efficiency), median(pace), count() by week plot efficiency vs distance compare none ``` Define fields before referring to them. Arithmetic supports `+`, `-`, `*`, `/`, parentheses, numeric constants, and earlier fields. Addition/subtraction requires matching units. Pace arithmetic uses seconds/km, duration uses hours; division of equal units gives `ratio`. Scalar multiplication keeps the field's unit. Combined units appear in validation and completion, e.g. `(km/h/bpm)` for efficiency. Missing inputs, zero divisors and nonfinite results yield missing values. Expressions cannot call functions, access attributes, index values or execute code. ## Filters and periods ```text runs during 2026-07-01 to 2026-09-30 where distance between 8 and 20 km and heart_rate < 150 bpm or title contains "Race" and has power summarize median(pace), count() by month compare previous period ``` Numeric operators: `=`, `!=`, `<`, `<=`, `>`, `>=`, and inclusive `between`. `has` / `missing` test metric availability; title search is case-insensitive and uses a double-quoted string. AND binds more strongly than OR: the example selects 8–20 km runs below 150 bpm **or** race-titled runs with power. Parenthesized boolean filters are not supported; use separate AND chains joined by OR. Periods: `during last N days/weeks/months`, inclusive explicit date ranges, or `during all time`. Rolling periods may use `ending YYYY-MM-DD`. Dates and groups use your account timezone. Supported relative ranges are 1–732 days, 1–104 weeks, 1–24 calendar months; explicit ranges cover at most 732 days. All time covers the complete recorded history through today and requires `compare none`. Comparisons otherwise use an equal-length preceding period. Time series align previous dates by elapsed calendar days; categorical groups use matching categories. ## Grouping, ranking and limits Group with `by day/week/month/year`, `by weekday`, `by hour`, `by run`, `by all`, or `by distance bins 5 km`. Weeks start Monday. Numeric bins are half-open ranges `[lower, upper)` anchored at zero; negative inputs use negative bins. Missing bin values are excluded. Time groups include empty calendar buckets, weekday/hour include all categories; numeric bins include observed bins. - `sort by FIELD asc/desc` orders runs; missing values sort last. `date` is allowed. - `take N` selects the first sorted runs **before** grouping, also for comparison. - `having SUMMARY OP VALUE UNIT` filters groups after aggregation. Multiple having clauses combine with AND; a summary need not be displayed to filter on it. - `rank by SUMMARY asc/desc` orders groups; the ranking summary must be selected. - `limit N` limits groups; without ranking, time groups retain the most recent N, categorical groups retain the first N in category order. The order is history-derived metrics → date/per-run filters → sort/take → groups → having → rank/limit → displayed cohort → chart trends. Cards and contributing runs reflect the displayed cohort. Limits are 1–1,000 runs/groups, six displayed summaries, eight calculated fields, twenty numeric filter conditions and ten having clauses. Wider grouping or limits are required above 1,000 displayed groups. ## Trends, relationships and export ```text runs during last 24 weeks summarize distance, count(), weighted_mean(pace) by week trend rolling 4 buckets compare none ``` Time groups support `trend cumulative`, `trend rolling N buckets` (1–52), `trend change`, and `trend percent_change`. Rolling/cumulative summaries pool contributing runs and recalculate the selected statistic; they do not average bucket means. These windows operate on displayed buckets after having/limits. Change compares consecutive displayed buckets; the first, missing inputs, and percentage division by zero remain missing. Percentage change uses the absolute previous value as denominator. Trends cannot also rank groups. Charts support `chart auto/line/bar/none`. `plot X vs Y` adds a run-level scatter plot and Pearson correlation using complete pairs. Constant axes or fewer than two pairs have no correlation. Select a scatter point to open its activity. Select a chart point, group row, or period picker to inspect contributing runs. For cumulative/rolling chart points, drill-through shows the pooled runs; group rows show only the original group's runs. The grouped results table and cards always retain untransformed statistics. Evidence shows individual input values, not an aggregate applied to one run. **Export CSV** downloads untransformed grouped results. **Export runs** downloads all runs in the displayed cohort, not only the evidence page. CSV contains raw numeric precision; pace is seconds/km, duration is hours, and missing values are blank. Run titles are escaped against spreadsheet formula interpretation. All queries, predecessor links, scatter points and exports remain owner-scoped. Analysis operates on persisted whole-run summaries; it does not load raw samples or routes. Saved views are stored in this browser; bookmark URLs to share a query with yourself across devices. Queries cannot join other users or raw sample data.