Extend run stats DSL with statistical and temporal analysis
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user