@pond-ts/fit 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Peter Murphy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,229 @@
1
+ # pond-ts
2
+
3
+ **Highly optimised, fully typed Timeseries library for TypeScript**
4
+
5
+ Schema-driven events, composable batch transforms, push-based streaming
6
+ ingest, multi-entity partitioning, and an optional React integration —
7
+ all strict TypeScript end to end, all immutable.
8
+
9
+ **pond-ts** is the TypeScript-first successor to
10
+ [pondjs](https://github.com/esnet/pond), rewritten from scratch with a
11
+ focus on type safety, composability, and the live-streaming patterns
12
+ that pondjs never grew.
13
+
14
+ ```sh
15
+ npm install pond-ts # core
16
+ npm install @pond-ts/react # React hooks (optional)
17
+ ```
18
+
19
+ - **Typed schemas** — declare once, every transform downstream narrows
20
+ off it. `event.get('cpu')` returns `number | undefined` straight from
21
+ the schema; no `as` casts.
22
+ - **Batch + streaming with the same vocabulary** — `filter`, `map`,
23
+ `aggregate`, `rolling`, `diff`, `rate`, `fill`, `cumulative`,
24
+ `sample`, `reduce` all exist on both `TimeSeries` and `LiveSeries`.
25
+ - **Multi-entity by construction** — `partitionBy('host')` routes per
26
+ entity; `rolling` / `aggregate` / `fill` / `sample` over a partitioned
27
+ view all become per-entity automatically.
28
+ - **Bounded-memory streaming** — retention policies, eviction-aware
29
+ views, and sampling decouple downstream window length
30
+ from event rate at firehose loads (up to 500k events/sec on a
31
+ single node.js instance.)
32
+ - **Triggers** — for control of rolling emission cadences. Synchronised
33
+ partitioned rolling fires across partitions on every boundary.
34
+ - **Typed column extraction** — `series.column('cpu')` returns a
35
+ schema-narrowed typed column with single-pass reductions
36
+ (`min`/`max`/`sum`/`mean`/`stdev`/`median`/`percentile`/`minMax`),
37
+ index downsampling (`bin`), and a zero-copy `toFloat64Array()` for
38
+ canvas / WebGL draw loops — no per-event allocation on the hot path.
39
+ - **No legacy baggage**
40
+
41
+ ## Quick start: batch
42
+
43
+ ```ts
44
+ import { Sequence, TimeSeries } from 'pond-ts';
45
+
46
+ const schema = [
47
+ { name: 'time', kind: 'time' },
48
+ { name: 'cpu', kind: 'number' },
49
+ { name: 'requests', kind: 'number' },
50
+ { name: 'host', kind: 'string' },
51
+ ] as const;
52
+
53
+ const cpu = TimeSeries.fromJSON({
54
+ name: 'cpu',
55
+ schema,
56
+ rows: [
57
+ ['2025-01-01T00:00:00Z', 0.31, 120, 'host1'],
58
+ ['2025-01-01T00:01:00Z', 0.44, 135, 'host2'],
59
+ ['2025-01-01T00:02:00Z', 0.52, 141, 'host1'],
60
+ ['2025-01-01T00:03:00Z', 0.48, 128, 'host1'],
61
+ ['2025-01-01T00:04:00Z', 0.63, 166, 'host3'],
62
+ ],
63
+ });
64
+
65
+ const byMinute = cpu.aggregate(Sequence.every('1m'), {
66
+ cpu: 'avg',
67
+ requests: 'sum',
68
+ host: 'last',
69
+ });
70
+
71
+ const bands = cpu.baseline('cpu', { window: '2m', sigma: 2 });
72
+ // ^ appends rolling avg / sd / upper / lower in one pass.
73
+
74
+ const anomalies = cpu.outliers('cpu', { window: '2m', sigma: 2 });
75
+ // ^ schema-preserving filter — same columns, just the spikes.
76
+ ```
77
+
78
+ The full batch surface (`align`, `rolling`, `smooth`, `groupBy`, `join`,
79
+ `reduce`, `diff`, `rate`, `fill`, `dedupe`, `materialize`, `sample`,
80
+ `partitionBy`, `pivotByGroup`, …) follows the same shape: TimeSeries
81
+ in, TimeSeries out, schema preserved.
82
+
83
+ ## Quick start: live (streaming)
84
+
85
+ ```ts
86
+ import { LiveSeries, Sequence } from 'pond-ts';
87
+
88
+ // 1. Same schema; this is a live append buffer with retention.
89
+ const live = new LiveSeries({
90
+ name: 'cpu',
91
+ schema,
92
+ retention: { maxAge: '10m' }, // keep only the last 10 minutes
93
+ });
94
+
95
+ // 2. Push as events arrive. Each push is validated against the schema.
96
+ live.push([Date.now(), 0.45, 128, 'api-1']);
97
+
98
+ // 3. Compose live views — incremental, push-driven, eviction-aware.
99
+ const recentAvg = live.rolling('5m', { cpu: 'avg' });
100
+ recentAvg.on('event', (e) => render(e.get('cpu')));
101
+
102
+ // 4. Snapshot to a TimeSeries for batch analytics at any time.
103
+ const snap = live.toTimeSeries();
104
+ ```
105
+
106
+ The full live surface (`filter`, `map`, `select`, `window`, `aggregate`,
107
+ `rolling`, `reduce`, `diff`, `rate`, `pctChange`, `fill`, `cumulative`,
108
+ `sample`) is incremental — events flow, views emit, retention bounds
109
+ memory.
110
+
111
+ ## Quick start: multi-entity
112
+
113
+ `partitionBy` routes events into per-key buffers. Every stateful
114
+ operator downstream of `partitionBy` runs per-partition automatically:
115
+
116
+ ```ts
117
+ const perHost = cpu
118
+ .partitionBy('host')
119
+ .rolling('5m', { cpu: 'avg', cpu_sd: 'stdev' });
120
+
121
+ // .collect() fans the per-partition outputs back into a flat TimeSeries
122
+ // with the partition key auto-injected as a column.
123
+ const flat = perHost.collect();
124
+ ```
125
+
126
+ Same shape on the live side — `live.partitionBy('host')` returns a
127
+ `LivePartitionedSeries` whose `rolling` / `fill` / `diff` / `sample`
128
+ methods all maintain per-partition state.
129
+
130
+ ## Quick start: bounded-memory sampling
131
+
132
+ At firehose rates, a long rolling baseline blows the heap. `sample({
133
+ stride: N })` decouples baseline length from event rate; chain it
134
+ between `partitionBy` and `rolling`:
135
+
136
+ ```ts
137
+ // Per-host 1-in-10 stride feeding a per-host 5m baseline.
138
+ live
139
+ .partitionBy('host')
140
+ .sample({ stride: 10 })
141
+ .rolling('5m', { cpu_avg: 'avg', cpu_sd: 'stdev' });
142
+ ```
143
+
144
+ For visualization, the snapshot side ships reservoir sampling too —
145
+ single-pass Algorithm R, sorted by key, fixed point count regardless of
146
+ source size:
147
+
148
+ ```ts
149
+ const points = series.sample({ reservoir: { size: 500 } }).toRows();
150
+ // 500 uncorrelated points drawn uniformly from the source.
151
+ ```
152
+
153
+ ## Performance
154
+
155
+ pond-ts is faster on **every** comparable operation, with no regressions —
156
+ a **~17x** geometric-mean speedup across the measurable ops, plus a handful
157
+ of transforms (`select` / `rename`) that are **effectively instant** (O(1)
158
+ column rebinds, below the timer's resolution). The advantage grows with data
159
+ size.
160
+
161
+ | Category | Speedup (N=16k) | Notes |
162
+ | ----------------- | ---------------------------------------------------- | --------------------------------------------- |
163
+ | **Rate** | ~120x | Single columnar walk vs Pipeline |
164
+ | **Fill** | 77–87x | Single columnar pass vs Pipeline per strategy |
165
+ | **Aggregation** | 57–82x | O(N+B) bucketing vs O(N×B) Pipeline |
166
+ | **Statistics** | 18–80x | Typed-array reduce vs ImmutableJS iteration |
167
+ | **Alignment** | 42x | Forward cursor vs repeated binary search |
168
+ | **Construction** | 13x | Columnar intake vs ImmutableJS wrapping |
169
+ | **Chained** | 8x | Derived constructors vs per-step Pipeline |
170
+ | **Transforms** | `select`/`rename` instant; `collapse` 30x; `map` ~4x | Column reshapes vs Pipeline |
171
+ | **Event access** | 6x | Array indexing vs ImmutableJS `get()` |
172
+ | **Serialization** | 4x | Lightweight columnar representation |
173
+
174
+ See the [full benchmark results](website/docs/reference/benchmarks.mdx)
175
+ for detailed numbers. Run locally:
176
+
177
+ ```sh
178
+ npm run build && node packages/core/bench/vs-pondjs.cjs
179
+ ```
180
+
181
+ ## Documentation
182
+
183
+ The full guide is at **<https://pjm17971.github.io/pond-ts/>**.
184
+
185
+ - **[Start here](https://pjm17971.github.io/pond-ts/docs/)**
186
+ — five-minute walkthrough with batch, live, and React examples.
187
+ - **[Concepts](https://pjm17971.github.io/pond-ts/docs/start-here/concepts)**
188
+ — temporal keys, sequences, windowing, partitioning, triggers, late
189
+ data.
190
+ - **[Transforms reference](https://pjm17971.github.io/pond-ts/docs/pond-ts/transforms/queries)**
191
+ — every batch operator (queries, aggregation, alignment, rolling,
192
+ smoothing, sampling, cleaning, reshape, anomaly detection).
193
+ - **[Live reference](https://pjm17971.github.io/pond-ts/docs/pond-ts/live/live-series)**
194
+ — `LiveSeries`, live transforms, triggering.
195
+ - **[How-to guides](https://pjm17971.github.io/pond-ts/docs/how-to-guides)**
196
+ — building a dashboard, ingesting messy data.
197
+ - **[API reference (auto-generated)](https://pjm17971.github.io/pond-ts/generated-api/core/)**
198
+ — TypeDoc output, every public class and method.
199
+ - **[CHANGELOG](./CHANGELOG.md)** — what shipped in each release.
200
+
201
+ ## Examples
202
+
203
+ - **[pond-ts-dashboard](https://github.com/pjm17971/pond-ts-dashboard)**
204
+ — a working React dashboard that streams synthetic per-host CPU /
205
+ request metrics, computes per-host rolling baselines, flags anomalies
206
+ against ±σ bands, and renders everything as live line and bar charts
207
+ (~600 lines of TypeScript). Walked through end-to-end in
208
+ [Building a dashboard](website/docs/how-to-guides/dashboard-guide.mdx).
209
+
210
+ ## Develop
211
+
212
+ The repo is an npm-workspaces monorepo with two published packages
213
+ (`pond-ts`, `@pond-ts/react`). Node 18+ for runtime; Node 20+ for the
214
+ docs site (Docusaurus).
215
+
216
+ ```sh
217
+ npm install # one-time, hoists deps for both packages
218
+ npm run build # build both packages
219
+ npm test # runtime + type-level tests on both packages
220
+ npm run format # prettier write across the repo
221
+ npm run verify # format check + build + test (CI parity)
222
+ ```
223
+
224
+ `packages/core/` is the `pond-ts` package; `packages/react/` is
225
+ `@pond-ts/react`. Docs live in `website/`.
226
+
227
+ ## License
228
+
229
+ MIT
@@ -0,0 +1,254 @@
1
+ /**
2
+ * The `Activity` / `Section` façade — the ergonomic object model of the fitness
3
+ * library. A thin, memoizing skin over the functional
4
+ * operator core: `prepareActivity` (the canonical pond series + derived columns),
5
+ * `summaryFromPrepared`, the geo splitters, and the power/zone analytics. Every
6
+ * method delegates to a tested pure operator and hands back {@link quantities}
7
+ * (Distance, Power, Speed, …) instead of bare numbers.
8
+ *
9
+ * `Activity` is **profile-agnostic**: anything that needs an athlete's FTP, body
10
+ * weight, or zone definitions takes them as arguments (the caller resolves them
11
+ * from the athlete profile as-of the activity date). The object wraps only the
12
+ * activity's own evidence.
13
+ *
14
+ * `Section` is a range-with-metrics over the activity — a split, a recorded lap,
15
+ * or an arbitrary `[from, to]` slice — exposing the same quantity-typed analytics
16
+ * as the whole activity. (NOT `Segment`: that word is reserved for the story
17
+ * hierarchy, Story → Chapter → Segment → Activity. See the RFC.)
18
+ */
19
+ import type { ActivityMeta, ImportedActivity } from '../types.js';
20
+ import type { TrackSeries, DistanceEffort } from '../geo/index.js';
21
+ import { type PreparedActivity, type ActivitySummary, type ActivitySummaryOptions, type WindowChannelOptions, type ChannelProfile } from '../summary/index.js';
22
+ import { type PowerSummary, type PowerCurvePoint, type PowerEffort, type PowerZone } from '../power/index.js';
23
+ import { type ZoneTime } from '../zones/index.js';
24
+ import { Profile, type ZoneDef } from '../profile/index.js';
25
+ import { Distance, Elevation, Duration, Speed, Pace, Power, HeartRate, Cadence } from '../quantities.js';
26
+ /** The normalized metric bundle a {@link Section} is a quantity-typed view over.
27
+ * Produced three ways — a computed split, a recorded lap, or an arbitrary range
28
+ * — all reduced to the same shape (native SI). Optional fields are absent when
29
+ * the underlying channel wasn't recorded. */
30
+ export interface SectionMetrics {
31
+ label: string;
32
+ /** Elapsed seconds from activity start to the section's start / end. */
33
+ fromSec: number;
34
+ toSec: number;
35
+ /** Cumulative-distance window into the activity (metres) — the map-highlight
36
+ * anchor, mirroring [fromSec, toSec] on the distance axis. */
37
+ startMeters: number;
38
+ endMeters: number;
39
+ distanceMeters: number;
40
+ /** Wall-clock span (toSec − fromSec). */
41
+ durationSeconds: number;
42
+ /** Time actually moving — equals duration for computed splits. */
43
+ movingSeconds: number;
44
+ elevationGainMeters: number;
45
+ elevationLossMeters?: number | undefined;
46
+ normalizedWatts?: number | undefined;
47
+ avgWatts?: number | undefined;
48
+ maxWatts?: number | undefined;
49
+ avgHeartrate?: number | undefined;
50
+ maxHeartrate?: number | undefined;
51
+ avgCadence?: number | undefined;
52
+ avgSpeedMps?: number | undefined;
53
+ maxSpeedMps?: number | undefined;
54
+ }
55
+ /**
56
+ * A range of an activity with its own analytics — a split, a lap, or a slice.
57
+ * Quantity-typed views over a {@link SectionMetrics} bundle; nothing computes
58
+ * here (the producer already did), so accessors are pure getters.
59
+ */
60
+ export declare class Section {
61
+ private readonly m;
62
+ constructor(m: SectionMetrics);
63
+ get label(): string;
64
+ /** Elapsed `[from, to]` window into the activity, in seconds — the time anchor
65
+ * a contiguous Focus / range annotation maps onto. */
66
+ get fromSeconds(): number;
67
+ get toSeconds(): number;
68
+ /** Cumulative-distance window [start, end] into the activity (metres) — the
69
+ * anchor for a map-segment highlight, the distance-axis peer of from/toSeconds. */
70
+ get startMeters(): number;
71
+ get endMeters(): number;
72
+ distance(): Distance;
73
+ duration(): Duration;
74
+ movingTime(): Duration;
75
+ elevationGain(): Elevation;
76
+ elevationLoss(): Elevation | undefined;
77
+ normalizedPower(): Power | undefined;
78
+ avgPower(): Power | undefined;
79
+ maxPower(): Power | undefined;
80
+ avgHeartRate(): HeartRate | undefined;
81
+ maxHeartRate(): HeartRate | undefined;
82
+ avgCadence(): Cadence | undefined;
83
+ /** Average speed — the recorded average when present, else distance ÷ moving
84
+ * time. `undefined` only when neither is available (no distance / no time). */
85
+ avgSpeed(): Speed | undefined;
86
+ maxSpeed(): Speed | undefined;
87
+ /** Average pace — the inverse view of {@link avgSpeed}. */
88
+ pace(): Pace | undefined;
89
+ }
90
+ /** Channel values interpolated at one instant — what {@link Activity.at} returns.
91
+ * Only channels the activity carries are present. */
92
+ export interface Sample {
93
+ /** Elapsed seconds from the start (the query time, clamped to the activity). */
94
+ atSeconds: number;
95
+ distance?: Distance;
96
+ elevation?: Elevation;
97
+ heartRate?: HeartRate;
98
+ power?: Power;
99
+ cadence?: Cadence;
100
+ speed?: Speed;
101
+ }
102
+ /**
103
+ * The activity façade. Construct from an imported activity (`fromStreams`); read
104
+ * the canonical series (`timeSeries`), slice it (`splits` / `laps` / `range`),
105
+ * sample it (`at`), and run analytics (`power` / `powerCurve` / `bestEfforts` /
106
+ * `hrZones`). Immutable; derived results memoize on first call.
107
+ */
108
+ export declare class Activity {
109
+ private readonly prep;
110
+ private _summary?;
111
+ /** NaN-for-missing power, lazily cleaned; `null` once we know there is none. */
112
+ private _watts?;
113
+ /** Splits memoized per interval (metres) — the summary recompute isn't cheap. */
114
+ private readonly _splits;
115
+ private constructor();
116
+ /** Build from an imported activity (streams + metadata + optional laps) — the
117
+ * Strava / fixture path. `fromFit` / `fromGpx` / `fromTcx` land with ingest. */
118
+ static fromStreams(imported: ImportedActivity): Activity;
119
+ /** Session metadata — id, name, sport, start, totals. */
120
+ get meta(): ActivityMeta;
121
+ /** Whether the activity carries GPS positions (a map; otherwise indoor / GPS-off). */
122
+ get hasTrack(): boolean;
123
+ /** Whether a power channel was recorded. */
124
+ get hasPower(): boolean;
125
+ /** The canonical pond series (the escape hatch to the functional/pond layer). */
126
+ timeSeries(): TrackSeries;
127
+ /** The prepared per-sample columns + derived series — for consumers still on
128
+ * the functional path (e.g. the chart) while they migrate to the façade. */
129
+ prepared(): PreparedActivity;
130
+ /** The full journey summary (totals, channels, polyline, splits, laps). */
131
+ summary(options?: ActivitySummaryOptions): ActivitySummary;
132
+ /** Channels re-bucketed over a distance window `[startMeters, endMeters]` — the
133
+ * chart-zoom resolution path, finer than the whole-activity profile from
134
+ * {@link summary}. The façade home for the functional `windowChannels`. */
135
+ windowChannels(options: WindowChannelOptions): ChannelProfile[];
136
+ /** Total moving / elapsed / distance, quantity-typed. */
137
+ distance(): Distance;
138
+ elapsedTime(): Duration;
139
+ movingTime(): Duration;
140
+ /** Even-distance splits (per-km, per-mile, …) as Sections, in order. */
141
+ splits(interval: Distance): Section[];
142
+ /** Device-recorded laps as Sections (empty if the source recorded none). */
143
+ laps(): Section[];
144
+ /** An arbitrary slice `[from, to]` (elapsed time) as a Section, with metrics
145
+ * computed from the series over that window. Clamped to the activity. */
146
+ range(from: Duration, to: Duration, label?: string): Section;
147
+ /** Power summary (NP, IF, TSS, distribution, zones, curve) at the given FTP —
148
+ * `undefined` when no power was recorded. `elapsedSeconds` drives TSS. */
149
+ power(ftp: number): PowerSummary | undefined;
150
+ /** Mean-maximal power curve; `[]` when no power. */
151
+ powerCurve(durations?: number[]): PowerCurvePoint[];
152
+ /** Power best efforts at the canonical durations (+ W/kg if `weightKg`); `[]`
153
+ * when no power. */
154
+ bestEfforts(opts?: {
155
+ weightKg?: number;
156
+ durations?: number[];
157
+ }): PowerEffort[];
158
+ /** Distance best efforts — fastest time over each canonical distance window
159
+ * (400 m … marathon), with avg HR. Needs a time axis; `[]` otherwise. */
160
+ distanceBestEfforts(distances?: number[]): DistanceEffort[];
161
+ /** Time-in-zone for heart rate against the given zone definition; `[]` with no HR. */
162
+ hrZones(def: ZoneDef): ZoneTime[];
163
+ /** Time-in-zone for pace (from the derived speed) against `def`; `[]` with no time. */
164
+ paceZones(def: ZoneDef): ZoneTime[];
165
+ /** Bind an athlete {@link Profile} for the analytics that need FTP / body
166
+ * weight / zone definitions — power summary, time-in-zone, W/kg. Returns a
167
+ * {@link ProfiledActivity} whose slices (`splits` / `range` / `laps`) stay
168
+ * bound to the same profile (turtles all the way down). The bare `Activity`
169
+ * keeps only the profile-agnostic methods. */
170
+ usingProfile(profile: Profile): ProfiledActivity;
171
+ /** Channel values interpolated at one elapsed instant — the scrub / annotation
172
+ * anchor sample. Linear between the bracketing samples. */
173
+ at(t: Duration): Sample;
174
+ /** Cleaned (NaN-for-missing) power column, memoized; `undefined` if no power. */
175
+ private watts;
176
+ /** Compute a range's metrics from the columns over `[lo, hi]` (inclusive). */
177
+ private metricsForRange;
178
+ }
179
+ /**
180
+ * An {@link Activity} bound to an athlete {@link Profile} — the home for the
181
+ * analytics that need FTP / body weight / zone definitions. Obtained via
182
+ * `activity.usingProfile(bob)`. Its slices (`splits` / `range` / `laps`) return
183
+ * {@link ProfiledSection}s carrying the same profile, so the binding flows all
184
+ * the way down. The bare {@link Activity} keeps only the profile-agnostic
185
+ * methods, so a missing FTP / weight is a type-level absence, not a runtime
186
+ * `undefined`.
187
+ */
188
+ export declare class ProfiledActivity {
189
+ private readonly activity;
190
+ private readonly profile;
191
+ constructor(activity: Activity, profile: Profile);
192
+ /** Full power summary (NP, IF, TSS, distribution, zones, curve) at the
193
+ * profile's FTP. `undefined` when no power was recorded or no FTP is known. */
194
+ power(): PowerSummary | undefined;
195
+ /** Time in each Coggan power zone (FTP-relative); `[]` with no power or no FTP. */
196
+ byPowerZone(): PowerZone[];
197
+ /** Time in each heart-rate zone; `[]` with no HR or no HR zones on the profile. */
198
+ byHeartRateZone(): ZoneTime[];
199
+ /** Time in each pace zone; `[]` with no time axis or no pace zones on the profile. */
200
+ byPaceZone(): ZoneTime[];
201
+ /** Power best efforts (+ W/kg from the profile's body weight); `[]` with no power. */
202
+ bestEfforts(durations?: number[]): PowerEffort[];
203
+ /** Even-distance splits as profile-bound sections. */
204
+ splits(interval: Distance): ProfiledSection[];
205
+ /** Device-recorded laps as profile-bound sections (empty if none recorded). */
206
+ laps(): ProfiledSection[];
207
+ /** An arbitrary `[from, to]` slice as a profile-bound section. */
208
+ range(from: Duration, to: Duration, label?: string): ProfiledSection;
209
+ }
210
+ /**
211
+ * A {@link Section} bound to a {@link Profile} — adds the FTP / weight / zone
212
+ * analytics over the section's own window. Profile-agnostic metrics delegate to
213
+ * the underlying section; the zone / power methods recompute over the section's
214
+ * `[fromSeconds, toSeconds]` slice of the parent activity.
215
+ */
216
+ export declare class ProfiledSection {
217
+ private readonly activity;
218
+ private readonly section;
219
+ private readonly profile;
220
+ private _window?;
221
+ constructor(activity: Activity, section: Section, profile: Profile);
222
+ /** The underlying profile-agnostic section. */
223
+ get unprofiled(): Section;
224
+ get label(): string;
225
+ get fromSeconds(): number;
226
+ get toSeconds(): number;
227
+ get startMeters(): number;
228
+ get endMeters(): number;
229
+ distance(): Distance;
230
+ duration(): Duration;
231
+ movingTime(): Duration;
232
+ elevationGain(): Elevation;
233
+ elevationLoss(): Elevation | undefined;
234
+ normalizedPower(): Power | undefined;
235
+ avgPower(): Power | undefined;
236
+ maxPower(): Power | undefined;
237
+ avgHeartRate(): HeartRate | undefined;
238
+ maxHeartRate(): HeartRate | undefined;
239
+ avgCadence(): Cadence | undefined;
240
+ avgSpeed(): Speed | undefined;
241
+ maxSpeed(): Speed | undefined;
242
+ pace(): Pace | undefined;
243
+ /** Power summary over the section's window; `undefined` with no power / no FTP. */
244
+ power(): PowerSummary | undefined;
245
+ /** Time in each power zone over the section's window; `[]` with no power / FTP. */
246
+ byPowerZone(): PowerZone[];
247
+ /** Time in each HR zone over the section's window; `[]` with no HR / HR zones. */
248
+ byHeartRateZone(): ZoneTime[];
249
+ /** Time in each pace zone over the section's window; `[]` with no time / pace zones. */
250
+ byPaceZone(): ZoneTime[];
251
+ /** Slice the parent columns to `[fromSeconds, toSeconds]`, computed once. */
252
+ private window;
253
+ }
254
+ //# sourceMappingURL=index.d.ts.map