endurance-coach 0.1.0 → 1.0.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.
Files changed (75) hide show
  1. package/README.md +3 -0
  2. package/dist/cli.js +318 -35
  3. package/dist/expander/expander.d.ts +20 -0
  4. package/dist/expander/expander.js +339 -0
  5. package/dist/expander/index.d.ts +8 -0
  6. package/dist/expander/index.js +9 -0
  7. package/dist/expander/types.d.ts +169 -0
  8. package/dist/expander/types.js +6 -0
  9. package/dist/expander/zones.d.ts +50 -0
  10. package/dist/expander/zones.js +159 -0
  11. package/dist/index.d.ts +4 -0
  12. package/dist/index.js +9 -1
  13. package/dist/schema/compact-plan.d.ts +175 -0
  14. package/dist/schema/compact-plan.js +64 -0
  15. package/dist/schema/compact-plan.schema.d.ts +277 -0
  16. package/dist/schema/compact-plan.schema.js +205 -0
  17. package/dist/templates/index.d.ts +10 -0
  18. package/dist/templates/index.js +13 -0
  19. package/dist/templates/interpolate.d.ts +51 -0
  20. package/dist/templates/interpolate.js +204 -0
  21. package/dist/templates/loader.d.ts +19 -0
  22. package/dist/templates/loader.js +129 -0
  23. package/dist/templates/template.schema.d.ts +401 -0
  24. package/dist/templates/template.schema.js +101 -0
  25. package/dist/templates/template.types.d.ts +155 -0
  26. package/dist/templates/template.types.js +7 -0
  27. package/dist/templates/yaml-parser.d.ts +15 -0
  28. package/dist/templates/yaml-parser.js +18 -0
  29. package/package.json +2 -1
  30. package/templates/bike/CLAUDE.md +7 -0
  31. package/templates/bike/easy.yaml +38 -0
  32. package/templates/bike/endurance.yaml +42 -0
  33. package/templates/bike/hills.yaml +80 -0
  34. package/templates/bike/overunders.yaml +81 -0
  35. package/templates/bike/rest.yaml +16 -0
  36. package/templates/bike/sweetspot.yaml +80 -0
  37. package/templates/bike/tempo.yaml +79 -0
  38. package/templates/bike/threshold.yaml +83 -0
  39. package/templates/bike/vo2max.yaml +84 -0
  40. package/templates/brick/CLAUDE.md +7 -0
  41. package/templates/brick/halfironman.yaml +72 -0
  42. package/templates/brick/ironman.yaml +72 -0
  43. package/templates/brick/olympic.yaml +70 -0
  44. package/templates/brick/sprint.yaml +70 -0
  45. package/templates/plan-viewer.html +22 -22
  46. package/templates/run/CLAUDE.md +7 -0
  47. package/templates/run/easy.yaml +36 -0
  48. package/templates/run/fartlek.yaml +40 -0
  49. package/templates/run/hills.yaml +36 -0
  50. package/templates/run/intervals.1k.yaml +63 -0
  51. package/templates/run/intervals.400.yaml +63 -0
  52. package/templates/run/intervals.800.yaml +63 -0
  53. package/templates/run/intervals.mile.yaml +64 -0
  54. package/templates/run/long.yaml +41 -0
  55. package/templates/run/progression.yaml +49 -0
  56. package/templates/run/race.5k.yaml +36 -0
  57. package/templates/run/recovery.yaml +36 -0
  58. package/templates/run/rest.yaml +16 -0
  59. package/templates/run/strides.yaml +49 -0
  60. package/templates/run/tempo.yaml +56 -0
  61. package/templates/run/threshold.yaml +56 -0
  62. package/templates/strength/CLAUDE.md +7 -0
  63. package/templates/strength/core.yaml +56 -0
  64. package/templates/strength/foundation.yaml +65 -0
  65. package/templates/strength/full.yaml +73 -0
  66. package/templates/strength/maintenance.yaml +62 -0
  67. package/templates/swim/CLAUDE.md +7 -0
  68. package/templates/swim/aerobic.yaml +67 -0
  69. package/templates/swim/easy.yaml +51 -0
  70. package/templates/swim/openwater.yaml +60 -0
  71. package/templates/swim/rest.yaml +16 -0
  72. package/templates/swim/technique.yaml +67 -0
  73. package/templates/swim/threshold.yaml +75 -0
  74. package/templates/swim/vo2max.yaml +88 -0
  75. /package/bin/{claude-coach.js → endurance-coach.js} +0 -0
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Core Expander
3
+ *
4
+ * Converts compact training plans to expanded format for HTML rendering.
5
+ */
6
+ import { parseWorkoutRef, parseWeekRange } from "../schema/compact-plan.js";
7
+ import { interpolate, evaluateExpression, createContext } from "../templates/index.js";
8
+ import { calculateAthleteZones } from "./zones.js";
9
+ // ============================================================================
10
+ // Date Utilities
11
+ // ============================================================================
12
+ /**
13
+ * Get the day of week name from a Date.
14
+ */
15
+ function getDayOfWeekName(date) {
16
+ const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
17
+ return days[date.getDay()];
18
+ }
19
+ /**
20
+ * Format a Date as ISO date string (YYYY-MM-DD).
21
+ */
22
+ function formatDate(date) {
23
+ return date.toISOString().split("T")[0];
24
+ }
25
+ /**
26
+ * Add days to a date.
27
+ */
28
+ function addDays(date, days) {
29
+ const result = new Date(date);
30
+ result.setDate(result.getDate() + days);
31
+ return result;
32
+ }
33
+ /**
34
+ * Calculate the start date of the plan from the event date and total weeks.
35
+ */
36
+ function calculateStartDate(eventDate, totalWeeks, firstDayOfWeek) {
37
+ const event = new Date(eventDate);
38
+ // Go back totalWeeks * 7 days from event date
39
+ const start = addDays(event, -(totalWeeks * 7));
40
+ // Adjust to the first day of the week
41
+ const targetDay = firstDayOfWeek === "monday" ? 1 : 0;
42
+ const currentDay = start.getDay();
43
+ const diff = currentDay - targetDay;
44
+ const adjustedDiff = diff < 0 ? diff + 7 : diff;
45
+ return addDays(start, -adjustedDiff);
46
+ }
47
+ /**
48
+ * Map compact day abbreviation to day index (0-6, starting from Sunday).
49
+ */
50
+ function dayAbbrevToIndex(abbrev) {
51
+ const mapping = {
52
+ Sun: 0,
53
+ Mon: 1,
54
+ Tue: 2,
55
+ Wed: 3,
56
+ Thu: 4,
57
+ Fri: 5,
58
+ Sat: 6,
59
+ };
60
+ return mapping[abbrev] ?? -1;
61
+ }
62
+ /**
63
+ * Get the offset from the first day of week to a specific day.
64
+ */
65
+ function getDayOffset(dayAbbrev, firstDayOfWeek) {
66
+ const dayIndex = dayAbbrevToIndex(dayAbbrev);
67
+ const firstDayIndex = firstDayOfWeek === "monday" ? 1 : 0;
68
+ let offset = dayIndex - firstDayIndex;
69
+ if (offset < 0)
70
+ offset += 7;
71
+ return offset;
72
+ }
73
+ // ============================================================================
74
+ // Workout Expansion
75
+ // ============================================================================
76
+ /**
77
+ * Parse a workout reference and extract the template ID and parameters.
78
+ */
79
+ function parseWorkoutReference(ref) {
80
+ return parseWorkoutRef(ref);
81
+ }
82
+ /**
83
+ * Expand a single workout from its template reference.
84
+ */
85
+ export function expandWorkout(ref, workoutId, context, templates) {
86
+ const parsed = parseWorkoutReference(ref);
87
+ const template = templates.get(parsed.templateId);
88
+ if (!template) {
89
+ // Create a placeholder workout for unknown templates
90
+ return {
91
+ id: workoutId,
92
+ sport: "run",
93
+ type: "unknown",
94
+ name: `Unknown: ${parsed.templateId}`,
95
+ humanReadable: `Template not found: ${parsed.templateId}`,
96
+ completed: false,
97
+ };
98
+ }
99
+ // Build the full context with template params
100
+ const paramContext = buildParamContext(template, parsed.params);
101
+ const fullContext = {
102
+ ...context,
103
+ ...paramContext,
104
+ };
105
+ // Interpolate the human-readable description
106
+ const humanReadable = interpolate(template.humanReadable, fullContext);
107
+ // Calculate duration
108
+ let durationMinutes;
109
+ if (template.estimatedDuration !== undefined) {
110
+ if (typeof template.estimatedDuration === "number") {
111
+ durationMinutes = template.estimatedDuration;
112
+ }
113
+ else {
114
+ const result = evaluateExpression(template.estimatedDuration, fullContext);
115
+ if (typeof result === "number") {
116
+ durationMinutes = result;
117
+ }
118
+ else {
119
+ const parsed = parseFloat(result);
120
+ if (!isNaN(parsed)) {
121
+ durationMinutes = parsed;
122
+ }
123
+ }
124
+ }
125
+ }
126
+ return {
127
+ id: workoutId,
128
+ sport: template.sport,
129
+ type: template.type,
130
+ name: template.name,
131
+ durationMinutes,
132
+ primaryZone: template.targetZone,
133
+ rpe: template.rpe,
134
+ humanReadable,
135
+ completed: false,
136
+ };
137
+ }
138
+ /**
139
+ * Build parameter context from template defaults and provided params.
140
+ */
141
+ function buildParamContext(template, providedParams) {
142
+ const context = {};
143
+ if (!template.params) {
144
+ return context;
145
+ }
146
+ const paramNames = Object.keys(template.params);
147
+ // Apply defaults first
148
+ for (const [name, def] of Object.entries(template.params)) {
149
+ if (def.default !== undefined) {
150
+ context[name] = def.default;
151
+ }
152
+ }
153
+ // Override with provided params (positional)
154
+ for (let i = 0; i < providedParams.length && i < paramNames.length; i++) {
155
+ context[paramNames[i]] = providedParams[i];
156
+ }
157
+ return context;
158
+ }
159
+ // ============================================================================
160
+ // Week Expansion
161
+ // ============================================================================
162
+ /**
163
+ * Expand a single week from the compact format.
164
+ */
165
+ function expandWeek(compactWeek, weekStartDate, context, templates, firstDayOfWeek) {
166
+ const days = [];
167
+ let totalMinutes = 0;
168
+ const bySport = {};
169
+ // Create all 7 days of the week
170
+ for (let dayOffset = 0; dayOffset < 7; dayOffset++) {
171
+ const date = addDays(weekStartDate, dayOffset);
172
+ const dayOfWeek = getDayOfWeekName(date);
173
+ days.push({
174
+ date: formatDate(date),
175
+ dayOfWeek,
176
+ workouts: [],
177
+ });
178
+ }
179
+ // Populate workouts from the schedule
180
+ for (const [dayAbbrev, workoutRefs] of Object.entries(compactWeek.workouts)) {
181
+ const dayOffset = getDayOffset(dayAbbrev, firstDayOfWeek);
182
+ if (dayOffset < 0 || dayOffset >= 7)
183
+ continue;
184
+ const day = days[dayOffset];
185
+ const refs = Array.isArray(workoutRefs) ? workoutRefs : [workoutRefs];
186
+ for (let i = 0; i < refs.length; i++) {
187
+ const ref = refs[i];
188
+ const workoutId = `week${compactWeek.week}-${dayAbbrev.toLowerCase()}-${i + 1}`;
189
+ const workout = expandWorkout(ref, workoutId, context, templates);
190
+ day.workouts.push(workout);
191
+ // Update summary stats
192
+ if (workout.durationMinutes) {
193
+ totalMinutes += workout.durationMinutes;
194
+ const sport = workout.sport;
195
+ if (!bySport[sport]) {
196
+ bySport[sport] = { sessions: 0, hours: 0 };
197
+ }
198
+ bySport[sport].sessions += 1;
199
+ bySport[sport].hours += workout.durationMinutes / 60;
200
+ }
201
+ }
202
+ }
203
+ // Round hours to 1 decimal place
204
+ for (const sport of Object.keys(bySport)) {
205
+ bySport[sport].hours = Math.round(bySport[sport].hours * 10) / 10;
206
+ }
207
+ const summary = {
208
+ totalHours: Math.round((totalMinutes / 60) * 10) / 10,
209
+ bySport,
210
+ };
211
+ return {
212
+ weekNumber: compactWeek.week,
213
+ startDate: formatDate(weekStartDate),
214
+ endDate: formatDate(addDays(weekStartDate, 6)),
215
+ phase: compactWeek.phase,
216
+ focus: compactWeek.focus || "",
217
+ targetHours: compactWeek.targetHours || summary.totalHours,
218
+ days,
219
+ summary,
220
+ isRecoveryWeek: compactWeek.isRecoveryWeek || false,
221
+ };
222
+ }
223
+ // ============================================================================
224
+ // Phase Expansion
225
+ // ============================================================================
226
+ /**
227
+ * Expand phases from compact format.
228
+ */
229
+ function expandPhases(compact) {
230
+ return compact.phases.map((phase) => {
231
+ const weeks = parseWeekRange(phase.weeks);
232
+ const startWeek = Math.min(...weeks);
233
+ const endWeek = Math.max(...weeks);
234
+ return {
235
+ name: phase.name,
236
+ startWeek,
237
+ endWeek,
238
+ focus: phase.focus,
239
+ weeklyHoursRange: { low: 0, high: 0 }, // Will be calculated from weeks
240
+ keyWorkouts: phase.keyWorkouts || [],
241
+ physiologicalGoals: [],
242
+ };
243
+ });
244
+ }
245
+ // ============================================================================
246
+ // Main Expander
247
+ // ============================================================================
248
+ /**
249
+ * Expand a compact plan into the full format for HTML rendering.
250
+ */
251
+ export function expandPlan(compact, templates, options = {}) {
252
+ const firstDayOfWeek = compact.athlete.firstDayOfWeek || "monday";
253
+ const totalWeeks = compact.weeks.length;
254
+ // Calculate start date
255
+ const startDate = options.startDate || calculateStartDate(compact.athlete.eventDate, totalWeeks, firstDayOfWeek);
256
+ // Build interpolation context
257
+ const zonesForContext = compact.athlete.zones?.hr
258
+ ? {
259
+ hr: {
260
+ lthr: compact.athlete.zones.hr.lthr,
261
+ maxHR: compact.athlete.zones.hr.maxHR,
262
+ restingHR: compact.athlete.zones.hr.restingHR,
263
+ },
264
+ }
265
+ : undefined;
266
+ const context = createContext(compact.athlete.paces, zonesForContext);
267
+ // Calculate zones
268
+ const zones = calculateAthleteZones(compact.athlete.zones?.hr, compact.athlete.paces);
269
+ // Expand phases
270
+ const phases = expandPhases(compact);
271
+ // Expand weeks
272
+ const weeks = [];
273
+ for (let i = 0; i < compact.weeks.length; i++) {
274
+ const compactWeek = compact.weeks[i];
275
+ const weekStartDate = addDays(startDate, i * 7);
276
+ const expandedWeek = expandWeek(compactWeek, weekStartDate, context, templates, firstDayOfWeek);
277
+ weeks.push(expandedWeek);
278
+ }
279
+ // Calculate phase hour ranges from actual weeks
280
+ for (const phase of phases) {
281
+ const phaseWeeks = weeks.filter((w) => w.weekNumber >= phase.startWeek && w.weekNumber <= phase.endWeek);
282
+ if (phaseWeeks.length > 0) {
283
+ const hours = phaseWeeks.map((w) => w.summary.totalHours);
284
+ phase.weeklyHoursRange = {
285
+ low: Math.min(...hours),
286
+ high: Math.max(...hours),
287
+ };
288
+ }
289
+ }
290
+ // Build metadata
291
+ const now = new Date().toISOString();
292
+ const meta = {
293
+ id: `plan-${Date.now()}`,
294
+ athlete: compact.athlete.name,
295
+ event: compact.athlete.event,
296
+ eventDate: compact.athlete.eventDate,
297
+ planStartDate: formatDate(startDate),
298
+ planEndDate: formatDate(addDays(startDate, totalWeeks * 7 - 1)),
299
+ createdAt: now,
300
+ updatedAt: now,
301
+ totalWeeks,
302
+ generatedBy: "Endurance Coach",
303
+ };
304
+ // Build preferences
305
+ const unit = compact.athlete.unit || "km";
306
+ const preferences = {
307
+ swim: "meters",
308
+ bike: unit === "mi" ? "miles" : "kilometers",
309
+ run: unit === "mi" ? "miles" : "kilometers",
310
+ firstDayOfWeek,
311
+ };
312
+ return {
313
+ version: "1.0",
314
+ meta,
315
+ preferences,
316
+ zones,
317
+ phases,
318
+ weeks,
319
+ raceStrategy: compact.raceStrategy ? { ...compact.raceStrategy } : undefined,
320
+ };
321
+ }
322
+ /**
323
+ * Validate that all workout references in a compact plan have corresponding templates.
324
+ */
325
+ export function validateWorkoutRefs(compact, templates) {
326
+ const errors = [];
327
+ for (const week of compact.weeks) {
328
+ for (const [day, refs] of Object.entries(week.workouts)) {
329
+ const refArray = Array.isArray(refs) ? refs : [refs];
330
+ for (const ref of refArray) {
331
+ const parsed = parseWorkoutReference(ref);
332
+ if (!templates.has(parsed.templateId)) {
333
+ errors.push(`Week ${week.week}, ${day}: Unknown template "${parsed.templateId}"`);
334
+ }
335
+ }
336
+ }
337
+ }
338
+ return errors;
339
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Expander Module
3
+ *
4
+ * Re-exports all expander-related types and functions.
5
+ */
6
+ export type { ExpandedPlan, ExpandedWeek, ExpandedDay, ExpandedWorkout, ExpandedPhase, ExpandedWeekSummary, ExpandedHRZones, ExpandedHRZone, ExpandedPaceZones, ExpandedPaceZone, ExpandedAthleteZones, ExpandedPlanMeta, ExpandedUnitPreferences, ExpansionOptions, } from "./types.js";
7
+ export { expandPlan, expandWorkout, validateWorkoutRefs } from "./expander.js";
8
+ export { calculateHRZones, calculatePaceZones, calculateAthleteZones, parsePace, formatPace, getHRZoneForValue, getPaceZoneForValue, } from "./zones.js";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Expander Module
3
+ *
4
+ * Re-exports all expander-related types and functions.
5
+ */
6
+ // Core expander
7
+ export { expandPlan, expandWorkout, validateWorkoutRefs } from "./expander.js";
8
+ // Zone calculations
9
+ export { calculateHRZones, calculatePaceZones, calculateAthleteZones, parsePace, formatPace, getHRZoneForValue, getPaceZoneForValue, } from "./zones.js";
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Expander Types
3
+ *
4
+ * Types for the plan expansion process - converting compact plans to expanded format.
5
+ */
6
+ import type { Sport } from "../schema/compact-plan.js";
7
+ /**
8
+ * A fully expanded workout with all variables interpolated.
9
+ */
10
+ export interface ExpandedWorkout {
11
+ id: string;
12
+ sport: Sport;
13
+ type: string;
14
+ name: string;
15
+ description?: string;
16
+ durationMinutes?: number;
17
+ primaryZone?: string;
18
+ rpe?: string;
19
+ humanReadable: string;
20
+ completed: boolean;
21
+ }
22
+ /**
23
+ * A day in the expanded plan.
24
+ */
25
+ export interface ExpandedDay {
26
+ date: string;
27
+ dayOfWeek: string;
28
+ workouts: ExpandedWorkout[];
29
+ }
30
+ /**
31
+ * Summary statistics for a training week.
32
+ */
33
+ export interface ExpandedWeekSummary {
34
+ totalHours: number;
35
+ bySport: {
36
+ [sport: string]: {
37
+ sessions: number;
38
+ hours: number;
39
+ };
40
+ };
41
+ }
42
+ /**
43
+ * A week in the expanded plan.
44
+ */
45
+ export interface ExpandedWeek {
46
+ weekNumber: number;
47
+ startDate: string;
48
+ endDate: string;
49
+ phase: string;
50
+ focus: string;
51
+ targetHours: number;
52
+ days: ExpandedDay[];
53
+ summary: ExpandedWeekSummary;
54
+ isRecoveryWeek: boolean;
55
+ }
56
+ /**
57
+ * A calculated heart rate zone.
58
+ */
59
+ export interface ExpandedHRZone {
60
+ zone: number;
61
+ name: string;
62
+ percentLow: number;
63
+ percentHigh: number;
64
+ hrLow: number;
65
+ hrHigh: number;
66
+ }
67
+ /**
68
+ * Full heart rate zone configuration.
69
+ */
70
+ export interface ExpandedHRZones {
71
+ lthr: number;
72
+ maxHR?: number;
73
+ restingHR?: number;
74
+ zones: ExpandedHRZone[];
75
+ }
76
+ /**
77
+ * A calculated pace zone.
78
+ */
79
+ export interface ExpandedPaceZone {
80
+ zone: string;
81
+ name: string;
82
+ pace: string;
83
+ paceSeconds: number;
84
+ }
85
+ /**
86
+ * Full pace zone configuration.
87
+ */
88
+ export interface ExpandedPaceZones {
89
+ thresholdPace: string;
90
+ thresholdPaceSeconds: number;
91
+ zones: ExpandedPaceZone[];
92
+ }
93
+ /**
94
+ * All athlete zones after calculation.
95
+ */
96
+ export interface ExpandedAthleteZones {
97
+ run?: {
98
+ hr?: ExpandedHRZones;
99
+ pace?: ExpandedPaceZones;
100
+ };
101
+ bike?: {
102
+ hr?: ExpandedHRZones;
103
+ };
104
+ maxHR?: number;
105
+ restingHR?: number;
106
+ }
107
+ /**
108
+ * A training phase in the expanded plan.
109
+ */
110
+ export interface ExpandedPhase {
111
+ name: string;
112
+ startWeek: number;
113
+ endWeek: number;
114
+ focus: string;
115
+ weeklyHoursRange: {
116
+ low: number;
117
+ high: number;
118
+ };
119
+ keyWorkouts: string[];
120
+ physiologicalGoals: string[];
121
+ }
122
+ /**
123
+ * Metadata for the expanded plan.
124
+ */
125
+ export interface ExpandedPlanMeta {
126
+ id: string;
127
+ athlete: string;
128
+ event: string;
129
+ eventDate: string;
130
+ planStartDate: string;
131
+ planEndDate: string;
132
+ createdAt: string;
133
+ updatedAt: string;
134
+ totalWeeks: number;
135
+ generatedBy: string;
136
+ }
137
+ /**
138
+ * Unit preferences for the expanded plan.
139
+ */
140
+ export interface ExpandedUnitPreferences {
141
+ swim: "meters" | "yards";
142
+ bike: "kilometers" | "miles";
143
+ run: "kilometers" | "miles";
144
+ firstDayOfWeek: "monday" | "sunday";
145
+ }
146
+ /**
147
+ * The complete expanded training plan.
148
+ * This format is consumed by the HTML renderer.
149
+ */
150
+ export interface ExpandedPlan {
151
+ version: "1.0";
152
+ meta: ExpandedPlanMeta;
153
+ preferences: ExpandedUnitPreferences;
154
+ zones: ExpandedAthleteZones;
155
+ phases: ExpandedPhase[];
156
+ weeks: ExpandedWeek[];
157
+ raceStrategy?: Record<string, unknown>;
158
+ }
159
+ /**
160
+ * Options for the expansion process.
161
+ */
162
+ export interface ExpansionOptions {
163
+ /** The start date for the plan (defaults to calculating from event date) */
164
+ startDate?: Date;
165
+ /** Whether to validate template references */
166
+ validateTemplates?: boolean;
167
+ /** Whether to include structured workout data (for device export) */
168
+ includeStructure?: boolean;
169
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Expander Types
3
+ *
4
+ * Types for the plan expansion process - converting compact plans to expanded format.
5
+ */
6
+ export {};
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Zone Calculator
3
+ *
4
+ * Calculates training zone ranges from threshold values.
5
+ * Uses standard physiological percentages for zone calculations.
6
+ */
7
+ import type { HRZoneConfig, AthletePaces } from "../schema/compact-plan.js";
8
+ import type { ExpandedHRZones, ExpandedHRZone, ExpandedPaceZones, ExpandedPaceZone, ExpandedAthleteZones } from "./types.js";
9
+ /**
10
+ * Calculate heart rate zones from LTHR.
11
+ *
12
+ * Uses the standard 5-zone model with percentages of LTHR.
13
+ */
14
+ export declare function calculateHRZones(config: HRZoneConfig): ExpandedHRZones;
15
+ /**
16
+ * Parse a pace string into seconds per unit.
17
+ *
18
+ * @example
19
+ * parsePace("5:30/km") // { seconds: 330, unit: "km" }
20
+ * parsePace("8:00/mi") // { seconds: 480, unit: "mi" }
21
+ * parsePace("5:30") // { seconds: 330, unit: undefined }
22
+ */
23
+ export declare function parsePace(pace: string): {
24
+ seconds: number;
25
+ unit?: string;
26
+ };
27
+ /**
28
+ * Format seconds back to a pace string.
29
+ *
30
+ * @example
31
+ * formatPace(330, "km") // "5:30/km"
32
+ * formatPace(480) // "8:00"
33
+ */
34
+ export declare function formatPace(seconds: number, unit?: string): string;
35
+ /**
36
+ * Calculate pace zones from threshold pace.
37
+ */
38
+ export declare function calculatePaceZones(thresholdPace: string): ExpandedPaceZones;
39
+ /**
40
+ * Calculate all athlete zones from compact plan inputs.
41
+ */
42
+ export declare function calculateAthleteZones(hrConfig?: HRZoneConfig, paces?: AthletePaces): ExpandedAthleteZones;
43
+ /**
44
+ * Get the HR zone for a given heart rate.
45
+ */
46
+ export declare function getHRZoneForValue(hr: number, zones: ExpandedHRZones): ExpandedHRZone | undefined;
47
+ /**
48
+ * Get the pace zone for a given pace.
49
+ */
50
+ export declare function getPaceZoneForValue(paceSeconds: number, zones: ExpandedPaceZones): ExpandedPaceZone | undefined;