Add saved analysis workspace and live dashboard statistics

This commit is contained in:
Peter Stockings
2026-10-03 12:27:20 +10:00
parent 5ede1a01d2
commit e6e2f22479
31 changed files with 1986 additions and 131 deletions
+149
View File
@@ -1,5 +1,154 @@
# Run analysis DSL
## Account library and live dashboard
Save an analysis with a name, investigation collection and notes. **Save as new**
or **Duplicate** creates an independent query to explore another question.
**Pin live analysis** adds it to your personal dashboard, where it can display a
plot, summary values or a grouped table. Width and order are saved to your
account. All queries and widget requests enforce owner scope, including shared
friend profiles. Dashboard widgets refresh on load/focus and every five minutes
while visible; each widget retains its own query date range. Relative periods
advance with the calendar. Results are recomputed rather than saved as snapshots.
## Matched effort, reference ranges and persistent deviations
```text
runs
during last 12 weeks
where pace between 5:20 and 5:40 /km
and elevation_per_km <= 10 m/km
and has heart_rate
summarize weighted_mean(heart_rate) as "Matched-pace HR"
by week
compare none
baseline previous 90 days
flag above baseline 1.5 sd min 3 runs sustained 2 buckets
chart line
```
The baseline is the stated number of local calendar days immediately **before
the query starts**, filtered with the same per-input conditions. It never uses
the current cohort. The center uses the selected summary function; its range is
center ± threshold × population standard deviation of individual matched
inputs. This is a descriptive range, not a confidence interval or a diagnostic
threshold. At least ten usable baseline inputs, and nonzero variation, are
required. Totals, counts and standard deviations do not receive baseline bands.
`above`, `below` and `outside` are supported. Flags require the minimum usable
inputs in **each** consecutive calendar bucket; missing, insufficient, in-range
or opposite-direction buckets reset the streak. Only the bucket completing the
streak and following qualifying buckets are marked. The grammar uses `runs` for
the minimum count even for laps/segments; the displayed coverage uses the actual
source. Segments from one run are correlated observations, so a high segment
count does not imply many independent sessions. Calendar grouping is required;
trend transforms and group removal/ranking cannot be combined with flags.
Terrain, conditions, warm-up and sensor differences can still affect matched
effort. Inspect the actual inputs and workload before interpreting a departure.
## Recording-level exploration
```text
segments
during last 12 weeks
where pace between 5:20 and 5:40 /km
and pace_variability <= 5 %
and sensor_coverage >= 80 %
and duration >= 4 min
summarize weighted_mean(heart_rate)
by week
compare none
chart line
```
`segments` expands persisted five-minute **moving-time** segments; the final
partial segment is retained, so filter duration when complete segments matter.
The segments accumulate usable moving intervals across pauses/gaps rather than
claiming five uninterrupted minutes. Intervals require increasing timestamps
with at most ten seconds between recordings, positive plausible movement
(0.5–15 m/s), and increasing distance. Missing sensor values are not interpolated.
HR is duration weighted. Ascent requires elevation on at least 80% of usable
moving intervals; otherwise it is missing. Ascent is the sum of positive raw
altitude increments and can be sensitive to noise. Segment dates are the parent
run's local start date; selecting one opens that run.
`laps` expands recorded lap distance, timer duration, HR and persisted GAP. Other
sensor/terrain metrics remain missing when the lap does not record them. Neither
source loads raw samples during a query. Their `count()` counts inputs; `by run`
gives each lap/segment its own row. Run-history metrics and custom rolling
calculations require `runs`.
Additional recording-derived metrics:
- `pace_variability`: duration-weighted **speed** coefficient of variation (%).
- `sensor_coverage`: usable moving time with HR / usable moving time (%), excluding gaps and pauses.
- `heart_rate_drift`: percent loss of speed/HR efficiency between moving-time halves. At least four moving minutes and ≥80% HR coverage in each half are required. Positive means lower efficiency later; warm-up and terrain are included.
- `gap`: modelled grade-adjusted pace in seconds/km, formatted as min/km.
New imports calculate these; older activities require `backfill-analysis`.
## Visualization and named series
`chart auto|line|bar|area|histogram|box|heatmap|stacked|none` controls presentation.
`display overlay` combines series with an axis per unit; `display separate` is
the default. Summary labels use JSON double-quoted strings:
```text
summarize sum(distance) as "Weekly distance", count() as "Sessions"
reference heart_rate = 150 bpm
plot pace vs heart_rate
color by elevation_per_km
size by distance
```
References require the selected raw metric and compatible units. Scatter color
and size require a scatter plot; missing encoding values use midpoint styling.
Correlation uses paired per-input values, not grouped summaries.
Histograms bin **individual input values** using up to 30 equal-width bins;
the final bin includes its upper endpoint. Boxes show min, p25, median, p75 and
max per group, with no inferred outlier rule. Distribution charts show current
inputs, while comparison cards retain the previous cohort. Calendar heatmaps
require `by day` and retain every day. Missing data differs from a zero count.
Distribution/heatmap overlays are not supported. PNG download exports the plot;
CSV exports the raw underlying grouped or per-input values.
## Named training cohorts
```text
runs
during last 12 weeks
summarize sum(distance)
by week
compare none
cohort "Lower HR" where heart_rate <= 145 bpm
cohort "Higher HR" where heart_rate > 145 bpm
chart stacked
```
Up to four named numeric conditions partition inputs in declaration order: the
**first matching cohort wins**, avoiding double counting. Unmatched/missing
inputs appear in Other / missing. Stacking requires one additive summary
(sum/count, distance, duration, elevation or calories), with no comparison,
baseline or trend. Thresholds are user-selected; names do not infer physiological zones.
## Custom rolling calculations and yearly comparisons
```text
let hr_14d = rolling_mean(heart_rate, 14 days)
let km_10d = rolling_sum(distance, 10 days)
let hr_count = rolling_count(heart_rate, 14 days)
compare previous year
```
Rolling functions use complete owner-scoped run history before filters, include
the current run, and exclude the exact lower elapsed-time boundary. They accept
1–365 days and an earlier metric/calculation. Means are unweighted, sums exclude
missing inputs, and counts count usable inputs. Calendar-bucket rolling trends
remain separate operations. `compare previous year` uses the same local-date
interval one year earlier, clamping leap-day boundaries; time charts align by
elapsed days from each period's start.
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 `#`.