Durabl is a privacy-first endurance analytics tool that answers a practical question for cyclists:
Do I hold my performance late in a workout, and how trustworthy is that answer given the data in my FIT file?
The current application is a Blazor WebAssembly site backed by a reusable .NET analytics pipeline. Workout files are processed locally in the browser: Durabl does not upload or store them. A console application is also included for development, batch analysis, sensitivity testing, and FIT field inspection.
Durabl is an experimental analysis tool, not a medical device or a substitute for professional advice.
Average power and headline workout totals can hide what happens after fatigue accumulates. Durabl compares fresh and fatigued portions of an activity, reports the available signal quality, and explains when the data is insufficient for a defensible durability score.
The project follows two core rules:
- Never manufacture a cycling durability score from speed alone.
- Make limitations and uncertainty visible alongside every result.
- Select and analyze a
.fitfile entirely in the browser. - View sport, duration, distance, signal coverage, data tier, and durability status.
- Review warnings, limitations, notes, findings, and simple time-series charts.
- Download a durability profile as JSON, a Markdown report, and a FIT field inventory when available.
- Navigate directly to
/analyzewithout creating an account.
The browser implementation has no login, database, payment integration, backend upload endpoint, Strava or Garmin API integration, or server-side file storage.
- Decode Garmin FIT data with the Garmin FIT SDK.
- Merge timestamp-fragmented records and normalize activity samples.
- Measure power, heart-rate, cadence, GPS, and elevation signal coverage.
- Classify activities into
Basic,Endurance,DurabilityReady, orDurabilityReadyPlustiers. - Generate a versioned
durabl.durability_profile.v0.1result. - Analyze folders and produce per-file reports.
- Run bounded parameter sensitivity sweeps without changing production defaults.
- Inspect typed and raw FIT fields, including developer and unknown fields.
- Redact location- and device-like values from raw dumps.
- Optionally calculate clearly labelled experimental estimated-power fields from rider and bike inputs.
In the web application, the selected FIT file is read into browser memory and passed directly to the local WebAssembly analytics pipeline. If parsing fails, the error is shown in the browser; the application does not fall back to uploading the file.
The analytics boundary intentionally excludes file names, GPS coordinates, timestamps, raw samples, raw bytes, device serial numbers, rider weight, email addresses, and raw report JSON.
Raw FIT inspection is different: raw dumps can contain GPS coordinates, device identifiers, profile details, and other personal telemetry. Use both redaction flags for safer inspection and review every generated file before sharing it:
dotnet run --project Durabl.FitSpike -- --inspect-folder samples/input `
--output samples/output `
--include-raw-dump `
--redact-location `
--redact-deviceGenerated reports and private sample inputs are ignored by Git.
A cycling durability score is produced only when all of these gates pass:
- The activity is cycling.
- Recorded power coverage is at least 70%.
- Activity duration is at least 30 minutes.
When a gate fails, DurabilityScore remains null and UnavailableReason explains why. Experimental estimated power never substitutes for recorded power in durability scoring.
Heart rate and cadence enrich interpretation but do not replace power. Heart-rate temporal quality is evaluated independently from raw coverage, and sparse sampling is reported as a warning. Cadence can qualify an activity for the highest data tier but is not required for the basic power-gated result.
The analyzer also exposes FatiguedExceededFresh when a no-decay result is caused by fatigued maximal mean power exceeding fresh maximal mean power. Consumers should explain that condition instead of presenting an unexplained perfect score.
| Path | Purpose |
|---|---|
Durabl.Web.Wasm/ |
Blazor WebAssembly application and browser report UI |
Durabl.FitSpike/ |
Reusable FIT processing, analytics, reporting, and CLI |
Durabl.FitSpike.Tests/ |
Unit, integration, serialization, and real-file tests |
samples/ |
Sample-corpus structure, licensing notes, and local output location |
scripts/ |
Bounded sample acquisition and validation tooling |
publish/ |
Tracked static deployment currently used for Cloudflare hosting |
- .NET 10 SDK
- A modern browser with WebAssembly support for the web application
NuGet restores the Garmin FIT SDK and the remaining project dependencies.
dotnet restore Durabl.slnx
dotnet run --project Durabl.Web.Wasm/Durabl.Web.Wasm.csprojOpen the local URL printed by .NET. The root route redirects to /analyze.
Analyze one FIT file:
dotnet run --project Durabl.FitSpike -- "C:\path\to\activity.fit"Include diagnostics:
dotnet run --project Durabl.FitSpike -- "C:\path\to\activity.fit" --diagnosticsAnalyze a directory:
dotnet run --project Durabl.FitSpike -- `
--analyze samples/input `
--output samples/outputRun a parameter sensitivity sweep:
dotnet run --project Durabl.FitSpike -- `
--sensitivity-sweep samples/input `
--output samples/outputInspect raw fields safely:
dotnet run --project Durabl.FitSpike -- `
--dump-raw-fit "C:\path\to\activity.fit" `
--output samples/output `
--redact-location `
--redact-deviceAdd rider inputs for experimental estimated power:
dotnet run --project Durabl.FitSpike -- "C:\path\to\activity.fit" `
--rider-weight 82 `
--bike-weight 9.5 `
--height 1.78 `
--position road `
--terrain road--bike-weight defaults to 12 kg. Rider weight and height must be supplied together. Estimated-power fields remain experimental and do not unlock durability scoring.
The current profile contract uses:
durabl.durability_profile.v0.1
This is the schema contract version, not the application version. The contract includes the gated durability result, unavailable reasons, data-quality information, and the FatiguedExceededFresh condition. Consumers should tolerate additive fields while the renderer-facing contract is completed.
dotnet build Durabl.slnx
dotnet test Durabl.slnxThe test suite covers normalization, signal analysis, data-tier classification, split analysis, fatigue markers, data quality, serialization, command-line parsing, raw-field output, redaction, web report mapping, and conditional real-file validation.
Create a release build with:
dotnet publish Durabl.Web.Wasm/Durabl.Web.Wasm.csproj -c ReleaseThe standard .NET output is:
Durabl.Web.Wasm/bin/Release/net10.0/publish/wwwroot
Cloudflare Pages can build from the repository using that command and output directory. This repository also intentionally retains publish/ because the current Cloudflare deployment is hosted from that checked-in static artifact. Do not remove or ignore publish/ without changing the deployment workflow first.
The application includes a _redirects rule so client-side routes such as /analyze resolve to index.html.
The samples/ tree separates files by provenance:
input/official/is reserved for public samples with verified permissive licensing and attribution.input/manual/is for user-created files that require explicit approval before committing.input/external-uncommitted/is for local validation files with unclear redistribution rights.input/rejected/contains fixtures excluded from active analysis, with their reason documented nearby.output/contains generated reports and remains ignored except for.gitkeep.
Do not commit personal or location-bearing FIT data without the data owner's permission. See samples/README.md for the corpus workflow.
- Cycling durability requires recorded power; speed-only activities receive lower-tier insight.
- Running-specific durability analysis is not implemented.
- Only FIT input is currently supported.
- Large FIT files may exceed browser memory limits.
- Garmin FIT SDK behavior should be validated across target browsers.
- Charts are intentionally lightweight and are not a full analysis workbench.
- Feedback entered in the current UI is local-only and is not persisted.
- The project does not yet include a redistributable cycling-with-power fixture with verified permission.
Near-term work focuses on:
- Completing the renderer-facing durability profile fields and regression coverage.
- Obtaining a permissioned cycling-with-power test fixture.
- Validating the report with real cyclists before tuning thresholds.
- Improving the browser report and mobile presentation.
- Adding TCX and GPX support with format-appropriate limitations.
Longer-term possibilities include running-specific analysis, opt-in local history and trends, comparable-workout detection, and an optional hosted service with user-controlled storage. Rule-based explanations remain preferred until multi-activity evidence justifies more advanced narrative generation.
The sensitivity sweep is a reporting tool, not an autonomous tuner. Analyzer defaults must not be changed automatically from sweep results.
Issues and focused pull requests are welcome. Please include tests for analytics changes, avoid committing generated reports or private workout files, and preserve explicit uncertainty and privacy boundaries in user-facing copy.
Durabl is available under the MIT License.