ACWR, EWMA ACWR, and exposure calculations now avoid treating unavailable pre-export history as zero-load rest days. EWMA also uses the corrected half-life mapping and overlapping moving-block bootstrap confidence bands.
Stream-based EF, decoupling, and PB calculations are more robust with ZIP exports, POSIXct stream timestamps, missing activity filenames, and activity streams with pauses, distance plateaus, or GPS bounce-backs.
Local Strava parsing is more tolerant of duplicate CSV headers, TCX timestamps
ending in Z, and TCX/GPX extension fields that use alternate namespace
prefixes for heart rate, cadence, or power.
Public examples and documentation now match the current offline ZIP workflow:
examples pass explicit activity_type values, stream-based functions show
export_dir, PB output documents time_basis, and ACWR wording uses
descriptive bands rather than injury-risk claims.
Regression tests were expanded across ACWR/EWMA, EF, decoupling, PBs, local activity loading, XML stream parsing, cohort-reference plotting, and public documentation wording.
pkgcheck now runs after the multi-platform R-CMD-check workflow.
The workflow uses the current ropensci-review-tools/pkgcheck-action
entrypoint, checks out the repository explicitly with actions/checkout@v5,
and skips the action's internal checkout. The report is treated as advisory so
rOpenSci/goodpractice recommendations remain visible without overriding the
already-green R CMD check gate.
load_local_activities() now fails earlier with a clearer Strava export
schema message. CSV files with localized or otherwise missing required
columns now report the missing English Strava columns and point users to the
Strava language setting before they request a fresh export.
calculate_ef() stream path now uses continuous-block steady-state
detection. Previously EF was computed as the median ratio across every
scattered point whose rolling CV cleared steady_cv_threshold, so an
interval workout with three short "steady islands" could silently pass
the min_steady_minutes gate. The stream path now mirrors
calculate_decoupling(): it uses rle() to find contiguous steady
runs and only accepts activities whose longest run lasts at least
min_steady_minutes. New output columns steady_duration_minutes,
n_steady_blocks, and sampling_interval_seconds make the accepted
block auditable; new status code "insufficient_steady_duration"
distinguishes this from the old "non_steady".
gap_hr stream-path fallback is now explicit. When the stream has
no grade-adjusted channel the function still falls back to plain
speed/HR, but the returned row now records this via
ef_metric_requested = "gap_hr", ef_metric_used = "speed_hr", and
the new status string "gap_stream_unavailable_fallback_to_speed".
Downstream consumers that need strict GAP semantics can now filter on
these columns instead of assuming the ef_metric argument equals the
metric actually computed.
Rolling windows are now time-based, not row-based. calculate_ef(),
calculate_decoupling(), and flag_quality() previously assumed 1 Hz
sampling (window_size <- 300 rows). The effective window silently
rescaled on 0.5 Hz smart-recording or multi-Hz streams. A new internal
estimate_sampling_interval() helper reads diff(time) and the three
functions now target wall-clock windows; the observed interval is
exposed as sampling_interval_seconds on EF/decoupling output and as
attr(result, "sampling_interval_seconds") on flag_quality output.
flag_quality() HR / power jump thresholds honour their "per-second"
documentation. max_hr_jump = 10 is now compared against
|dHR/dt| (bpm / s) rather than raw sample-to-sample differences; the
same change applies to max_pw_jump. Streams that are not exactly
1 Hz are therefore judged consistently across recording devices.
Training-load calculation now distinguishes rest days from
missing-data training days. compute_single_load() returns
list(value, status) instead of collapsing every non-computable case
to 0. calculate_acwr(), calculate_acwr_ewma(), and
calculate_exposure() gain a missing_load = c("zero", "na")
argument: "zero" (default) preserves the historical behaviour,
"na" keeps NA on days whose load could not be computed so the
rolling means visibly propagate the gap.
calculate_decoupling(stream_df = ..., return_diagnostics = TRUE)
now returns a one-row data frame exposing decoupling, status,
quality_score, hr_coverage, steady_duration_minutes, and
sampling_interval_seconds, so callers can distinguish the reason an
NA decoupling was produced. The default still returns a numeric for
backward compatibility.
calculate_pbs() adds a time_basis column (always "moving" in
the current implementation) and clarifies in roxygen that PBs are
moving-time best efforts because find_best_effort() operates on the
strictly-monotonic-distance subset (paused / plateau samples are
excluded from the candidate window).
flag_quality() surfaces the activity-level score as an attribute.
The per-row quality_score column is preserved for backward
compatibility but the same value is now also attached as
attr(result, "activity_quality_score") and its activity-level
semantics are documented.
calculate_cohort_reference() requires athlete_id by default.
Passing a frame with no athlete_id column previously silently
injected a synthetic "unknown" athlete and produced a band that
looked cohort-derived but was actually within-athlete. The function
now stop()s unless the caller explicitly opts in via
allow_unknown_athlete = TRUE.
missing_load = "zero" now warns when it silently absorbs data
gaps. calculate_acwr(), calculate_acwr_ewma() and
calculate_exposure() invoke a shared helper that scans the
per-activity load_status column produced by
compute_single_load(); if any activity returned a missing_* /
hr_out_of_range status and the caller kept the historical "zero"
default, the function now emits a one-shot warning() listing the
top statuses and recommending missing_load = "na". The default is
still "zero" for backward compatibility.calculate_pbs() now uses the same stream-parse call pattern as
calculate_decoupling(). Previously PB processing called
parse_activity_file(file.path(export_dir, activity$filename), export_dir) which relied on the zip resolver stripping the
export_dir/ prefix internally. The new call passes
activity$filename and export_dir directly, matching the
decoupling path and removing the redundant prefix hop.
calculate_decoupling() stream_df roxygen corrected. Earlier
drafts said "If provided, calculates decoupling for this data
directly, ignoring other parameters". The implementation has not
ignored those parameters since 1.0.4; the documentation now matches
the behaviour and explicitly lists which arguments stream_df
honours.
@return tables and the
Status vocabulary sections with every status string the two
functions can emit.PB time semantics section to calculate_pbs()
explaining elapsed-vs-moving interpretation; clarified that
elapsed_time and moving_time are compatibility columns that
carry the same time_seconds value and that time_basis is the
authoritative field.missing_load knob on every training-load entry
point.version updated from 1.0.4 to 1.0.5. The Quick Start
ZIP note now reflects calculate_pbs() and calculate_decoupling()
being zip-aware in 1.0.5.Test suite cleanup: Further streamlined from ~600 to 373 assertions. All tests now pass locally with zero warnings and zero skips.
Test idiom improvements: Replaced expect_true(is.data.frame()) with expect_s3_class(), expect_equal(length()) with expect_length(). Removed redundant gg-class checks already covered by vdiffr snapshots.
Test file consolidation: Deleted fragmented files (test-smoke-and-errors.R, test-uncovered-branches.R, etc.) and merged relevant tests into per-feature files.
Dependency cleanup: Removed purrr entirely (only used once via superseded purrr::transpose()). R CMD check now passes with 0 errors and 0 warnings.
Packaging: Excluded CITATION.cff from the R package build (it was flagged by R CMD check as non-standard), cleaned up NAMESPACE and .Rbuildignore.
Test suite rewrite: Reduced from ~1500 to ~200 focused tests with meaningful value assertions; added vdiffr snapshot testing for all plot functions (15 visual regression tests).
Bug fixes:
FITfileR::records() returns a listcalculate_ef_from_stream() column name mismatch (power vs watts)calculate_decoupling() column name mismatch (heart_rate vs heartrate)Analysis–plotting separation: All plot functions now require pre-computed data; passing analysis arguments emits a deprecation warning.
Custom S3 classes: All calculation functions return dedicated classes (athlytics_acwr, athlytics_ef, athlytics_decoupling, athlytics_pbs, athlytics_exposure).
Scientific references: Added references with DOIs throughout vignettes and roxygen documentation (Gabbett, Hulin, Impellizzeri, Coyle, Allen, Williams, etc.).
ACWR caveats: Added "Important Caveats" section discussing scientific debate on ACWR predictive validity.
New features:
gap_hr) support for EF calculationsmooth_per_activity_type option in plot_ef()plot_acwr()plot_exposure() risk zones no longer require an ACWR columncalculate_pbs()Documentation improvements:
pace_hr deprecated in favor of speed_hr)|> used in vignettesCode cleanup: Removed zzz.R, unused color palette functions, rStrava/mockery from Suggests, Strava API references; cleaned NAMESPACE imports.
CI: R CMD check now runs on 3 R versions (devel, release, oldrel-1) across 3 OS.
Shipped example files: inst/extdata/ contains example.fit, .gpx, .tcx.
Code style: Applied styler::style_pkg() formatting.
Runnable vignettes: Added executable demo chunks using built-in sample datasets so key plots render during build_vignettes().
Sample data naming: Renamed built-in datasets from athlytics_sample_* to sample_* and updated docs/examples accordingly.
Styling: Ran styler::style_pkg() to improve formatting consistency.
Reduced Cyclomatic Complexity: Refactored calculate_acwr() and calculate_exposure() by extracting shared load calculation logic into internal helper functions (calculate_daily_load_internal(), compute_single_load(), validate_load_metric_params()). This improves code maintainability and testability without changing the public API.
Dependency Cleanup: Removed unused viridis package from Imports. The package was declared as a dependency but never actually called (ggplot2's built-in scale_color_viridis_d() was used instead).
Documentation Fixes: Fixed Rd line width issues in plot_with_reference() examples.
API Naming Consistency: Added verb-first primary APIs and kept previous names as deprecated wrappers for backward compatibility.
calculate_cohort_reference() (replaces cohort_reference())summarize_quality() (replaces quality_summary())Build Configuration: Updated .Rbuildignore to properly exclude development files.
This major release transitions from Strava API to local data export processing, prioritizing user privacy and data ownership while eliminating API rate limits and authentication requirements.
Privacy-First Architecture: Complete shift from Strava API to local ZIP file processing
load_local_activities() function supports direct ZIP file loading (no manual extraction needed)Enhanced Documentation: Comprehensive documentation improvements across all core functions
calculate_acwr(), calculate_ef(), and calculate_decoupling() documentationMulti-Athlete Cohort Analysis: Improved support for research and team analytics
cohort_reference() and multi-athlete workflowsgroup_modify() for batch processingREADME & Package Updates
activities_df → activities_data)paper/paper.md and paper/paper.bib)
.gitignore to track README.md and paper/ directoryFor users upgrading from 0.1.x:
fetch_strava_activities() calls with load_local_activities("export.zip")activities_df → activities_data, plot_*() now accepts data directly\dontrun{} as advised) and ensuring metadata files meet all CRAN standards.This significant update enhances package reliability and ease of use by integrating sample datasets. This enables all examples to run offline and ensures core functionalities have undergone more rigorous, reproducible testing.
\donttest{} blocks.mockery, improving test robustness and parameter coverage.strava_oauth(...) scenarios for offline/testing contexts); providing accurate and refined documentation for data objects in R/data.R; fixing Roxygen import directives for precise namespace definition; improving help file readability through Rd line width adjustments; and optimizing package data loading by adding LazyData: true to DESCRIPTION.rStrava::get_activity_streams to direct Strava API calls using httr and jsonlite for fetching activity streams in calculate_decoupling. This aims to resolve previous errors but might impact performance and rate limiting.calculate_acwr error (condition has length > 1) by forcing evaluation before the dplyr pipe.plot_pbs usage in examples and test scripts to include the required distance_meters argument.httr, jsonlite) to DESCRIPTION file.rStrava.