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.
- package/README.md +3 -0
- package/dist/cli.js +318 -35
- package/dist/expander/expander.d.ts +20 -0
- package/dist/expander/expander.js +339 -0
- package/dist/expander/index.d.ts +8 -0
- package/dist/expander/index.js +9 -0
- package/dist/expander/types.d.ts +169 -0
- package/dist/expander/types.js +6 -0
- package/dist/expander/zones.d.ts +50 -0
- package/dist/expander/zones.js +159 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +9 -1
- package/dist/schema/compact-plan.d.ts +175 -0
- package/dist/schema/compact-plan.js +64 -0
- package/dist/schema/compact-plan.schema.d.ts +277 -0
- package/dist/schema/compact-plan.schema.js +205 -0
- package/dist/templates/index.d.ts +10 -0
- package/dist/templates/index.js +13 -0
- package/dist/templates/interpolate.d.ts +51 -0
- package/dist/templates/interpolate.js +204 -0
- package/dist/templates/loader.d.ts +19 -0
- package/dist/templates/loader.js +129 -0
- package/dist/templates/template.schema.d.ts +401 -0
- package/dist/templates/template.schema.js +101 -0
- package/dist/templates/template.types.d.ts +155 -0
- package/dist/templates/template.types.js +7 -0
- package/dist/templates/yaml-parser.d.ts +15 -0
- package/dist/templates/yaml-parser.js +18 -0
- package/package.json +2 -1
- package/templates/bike/CLAUDE.md +7 -0
- package/templates/bike/easy.yaml +38 -0
- package/templates/bike/endurance.yaml +42 -0
- package/templates/bike/hills.yaml +80 -0
- package/templates/bike/overunders.yaml +81 -0
- package/templates/bike/rest.yaml +16 -0
- package/templates/bike/sweetspot.yaml +80 -0
- package/templates/bike/tempo.yaml +79 -0
- package/templates/bike/threshold.yaml +83 -0
- package/templates/bike/vo2max.yaml +84 -0
- package/templates/brick/CLAUDE.md +7 -0
- package/templates/brick/halfironman.yaml +72 -0
- package/templates/brick/ironman.yaml +72 -0
- package/templates/brick/olympic.yaml +70 -0
- package/templates/brick/sprint.yaml +70 -0
- package/templates/plan-viewer.html +22 -22
- package/templates/run/CLAUDE.md +7 -0
- package/templates/run/easy.yaml +36 -0
- package/templates/run/fartlek.yaml +40 -0
- package/templates/run/hills.yaml +36 -0
- package/templates/run/intervals.1k.yaml +63 -0
- package/templates/run/intervals.400.yaml +63 -0
- package/templates/run/intervals.800.yaml +63 -0
- package/templates/run/intervals.mile.yaml +64 -0
- package/templates/run/long.yaml +41 -0
- package/templates/run/progression.yaml +49 -0
- package/templates/run/race.5k.yaml +36 -0
- package/templates/run/recovery.yaml +36 -0
- package/templates/run/rest.yaml +16 -0
- package/templates/run/strides.yaml +49 -0
- package/templates/run/tempo.yaml +56 -0
- package/templates/run/threshold.yaml +56 -0
- package/templates/strength/CLAUDE.md +7 -0
- package/templates/strength/core.yaml +56 -0
- package/templates/strength/foundation.yaml +65 -0
- package/templates/strength/full.yaml +73 -0
- package/templates/strength/maintenance.yaml +62 -0
- package/templates/swim/CLAUDE.md +7 -0
- package/templates/swim/aerobic.yaml +67 -0
- package/templates/swim/easy.yaml +51 -0
- package/templates/swim/openwater.yaml +60 -0
- package/templates/swim/rest.yaml +16 -0
- package/templates/swim/technique.yaml +67 -0
- package/templates/swim/threshold.yaml +75 -0
- package/templates/swim/vo2max.yaml +88 -0
- /package/bin/{claude-coach.js → endurance-coach.js} +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zone Calculator
|
|
3
|
+
*
|
|
4
|
+
* Calculates training zone ranges from threshold values.
|
|
5
|
+
* Uses standard physiological percentages for zone calculations.
|
|
6
|
+
*/
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Heart Rate Zone Calculation
|
|
9
|
+
// ============================================================================
|
|
10
|
+
/**
|
|
11
|
+
* Standard 5-zone heart rate model based on LTHR percentages.
|
|
12
|
+
*/
|
|
13
|
+
const HR_ZONE_DEFINITIONS = [
|
|
14
|
+
{ zone: 1, name: "Recovery", percentLow: 0, percentHigh: 81 },
|
|
15
|
+
{ zone: 2, name: "Aerobic", percentLow: 81, percentHigh: 89 },
|
|
16
|
+
{ zone: 3, name: "Tempo", percentLow: 89, percentHigh: 94 },
|
|
17
|
+
{ zone: 4, name: "Threshold", percentLow: 94, percentHigh: 100 },
|
|
18
|
+
{ zone: 5, name: "VO2max", percentLow: 100, percentHigh: 106 },
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Calculate heart rate zones from LTHR.
|
|
22
|
+
*
|
|
23
|
+
* Uses the standard 5-zone model with percentages of LTHR.
|
|
24
|
+
*/
|
|
25
|
+
export function calculateHRZones(config) {
|
|
26
|
+
const { lthr, maxHR, restingHR } = config;
|
|
27
|
+
const zones = HR_ZONE_DEFINITIONS.map((def) => ({
|
|
28
|
+
zone: def.zone,
|
|
29
|
+
name: def.name,
|
|
30
|
+
percentLow: def.percentLow,
|
|
31
|
+
percentHigh: def.percentHigh,
|
|
32
|
+
hrLow: Math.round(lthr * (def.percentLow / 100)),
|
|
33
|
+
hrHigh: Math.round(lthr * (def.percentHigh / 100)),
|
|
34
|
+
}));
|
|
35
|
+
return {
|
|
36
|
+
lthr,
|
|
37
|
+
maxHR,
|
|
38
|
+
restingHR,
|
|
39
|
+
zones,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// ============================================================================
|
|
43
|
+
// Pace Zone Calculation
|
|
44
|
+
// ============================================================================
|
|
45
|
+
/**
|
|
46
|
+
* Parse a pace string into seconds per unit.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* parsePace("5:30/km") // { seconds: 330, unit: "km" }
|
|
50
|
+
* parsePace("8:00/mi") // { seconds: 480, unit: "mi" }
|
|
51
|
+
* parsePace("5:30") // { seconds: 330, unit: undefined }
|
|
52
|
+
*/
|
|
53
|
+
export function parsePace(pace) {
|
|
54
|
+
const match = pace.match(/^(\d+):(\d{2})(?:\/(\w+))?$/);
|
|
55
|
+
if (!match) {
|
|
56
|
+
throw new Error(`Invalid pace format: ${pace}`);
|
|
57
|
+
}
|
|
58
|
+
const minutes = parseInt(match[1], 10);
|
|
59
|
+
const seconds = parseInt(match[2], 10);
|
|
60
|
+
const unit = match[3];
|
|
61
|
+
return {
|
|
62
|
+
seconds: minutes * 60 + seconds,
|
|
63
|
+
unit,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Format seconds back to a pace string.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* formatPace(330, "km") // "5:30/km"
|
|
71
|
+
* formatPace(480) // "8:00"
|
|
72
|
+
*/
|
|
73
|
+
export function formatPace(seconds, unit) {
|
|
74
|
+
const mins = Math.floor(seconds / 60);
|
|
75
|
+
const secs = Math.round(seconds % 60);
|
|
76
|
+
const base = `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
77
|
+
return unit ? `${base}/${unit}` : base;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Standard pace zone definitions based on Jack Daniels' VDOT system.
|
|
81
|
+
* Offsets are in seconds per km from threshold pace.
|
|
82
|
+
*/
|
|
83
|
+
const PACE_ZONE_DEFINITIONS = [
|
|
84
|
+
{ zone: "E", name: "Easy", offsetSeconds: 60 }, // +60s from threshold
|
|
85
|
+
{ zone: "M", name: "Marathon", offsetSeconds: 30 }, // +30s from threshold
|
|
86
|
+
{ zone: "T", name: "Threshold", offsetSeconds: 0 }, // At threshold
|
|
87
|
+
{ zone: "I", name: "Interval", offsetSeconds: -15 }, // -15s from threshold
|
|
88
|
+
{ zone: "R", name: "Repetition", offsetSeconds: -30 }, // -30s from threshold
|
|
89
|
+
];
|
|
90
|
+
/**
|
|
91
|
+
* Calculate pace zones from threshold pace.
|
|
92
|
+
*/
|
|
93
|
+
export function calculatePaceZones(thresholdPace) {
|
|
94
|
+
const { seconds: thresholdSeconds, unit } = parsePace(thresholdPace);
|
|
95
|
+
const zones = PACE_ZONE_DEFINITIONS.map((def) => {
|
|
96
|
+
const paceSeconds = thresholdSeconds + def.offsetSeconds;
|
|
97
|
+
return {
|
|
98
|
+
zone: def.zone,
|
|
99
|
+
name: def.name,
|
|
100
|
+
pace: formatPace(paceSeconds, unit),
|
|
101
|
+
paceSeconds,
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
thresholdPace,
|
|
106
|
+
thresholdPaceSeconds: thresholdSeconds,
|
|
107
|
+
zones,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// ============================================================================
|
|
111
|
+
// Combined Zone Calculation
|
|
112
|
+
// ============================================================================
|
|
113
|
+
/**
|
|
114
|
+
* Calculate all athlete zones from compact plan inputs.
|
|
115
|
+
*/
|
|
116
|
+
export function calculateAthleteZones(hrConfig, paces) {
|
|
117
|
+
const zones = {};
|
|
118
|
+
// Calculate HR zones if LTHR provided
|
|
119
|
+
if (hrConfig) {
|
|
120
|
+
zones.run = zones.run || {};
|
|
121
|
+
zones.run.hr = calculateHRZones(hrConfig);
|
|
122
|
+
// Also apply to bike if no separate bike HR zones
|
|
123
|
+
zones.bike = zones.bike || {};
|
|
124
|
+
zones.bike.hr = calculateHRZones(hrConfig);
|
|
125
|
+
zones.maxHR = hrConfig.maxHR;
|
|
126
|
+
zones.restingHR = hrConfig.restingHR;
|
|
127
|
+
}
|
|
128
|
+
// Calculate pace zones if threshold pace provided
|
|
129
|
+
if (paces?.threshold) {
|
|
130
|
+
zones.run = zones.run || {};
|
|
131
|
+
zones.run.pace = calculatePaceZones(paces.threshold);
|
|
132
|
+
}
|
|
133
|
+
return zones;
|
|
134
|
+
}
|
|
135
|
+
// ============================================================================
|
|
136
|
+
// Utility Functions
|
|
137
|
+
// ============================================================================
|
|
138
|
+
/**
|
|
139
|
+
* Get the HR zone for a given heart rate.
|
|
140
|
+
*/
|
|
141
|
+
export function getHRZoneForValue(hr, zones) {
|
|
142
|
+
return zones.zones.find((z) => hr >= z.hrLow && hr <= z.hrHigh);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get the pace zone for a given pace.
|
|
146
|
+
*/
|
|
147
|
+
export function getPaceZoneForValue(paceSeconds, zones) {
|
|
148
|
+
// Find the closest zone
|
|
149
|
+
let closest;
|
|
150
|
+
let minDiff = Infinity;
|
|
151
|
+
for (const zone of zones.zones) {
|
|
152
|
+
const diff = Math.abs(paceSeconds - zone.paceSeconds);
|
|
153
|
+
if (diff < minDiff) {
|
|
154
|
+
minDiff = diff;
|
|
155
|
+
closest = zone;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return closest;
|
|
159
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,4 +4,8 @@
|
|
|
4
4
|
* Public API for validating and working with training plans.
|
|
5
5
|
*/
|
|
6
6
|
export { validatePlan, validatePlanOrThrow, formatValidationErrors, getJsonSchema, TrainingPlanSchema, WorkoutSchema, TrainingWeekSchema, TrainingDaySchema, TrainingPhaseSchema, AthleteAssessmentSchema, AthleteZonesSchema, RaceStrategySchema, UnitPreferencesSchema, PlanMetaSchema, type TrainingPlan, type Workout, type TrainingWeek, type TrainingDay, type TrainingPhase, type AthleteAssessment, type AthleteZones, type RaceStrategy, type UnitPreferences, type ValidationResult, type ValidationError, } from "./schema/training-plan.schema.js";
|
|
7
|
+
export { validateCompactPlan, validateCompactPlanOrThrow, formatCompactValidationErrors, CompactPlanSchema, CompactAthleteSchema, CompactPhaseSchema, CompactWeekSchema, type CompactPlan, type CompactAthlete, type CompactPhase, type CompactWeek, type CompactWeekSchedule, type CompactRaceStrategy, type AthletePaces, type CompactValidationResult, type CompactValidationError, } from "./schema/compact-plan.schema.js";
|
|
8
|
+
export { parseWorkoutRef, parseWeekRange } from "./schema/compact-plan.js";
|
|
9
|
+
export { loadTemplates, loadTemplatesFromArray, getTemplatesPath, validateTemplate, validateTemplateOrThrow, interpolate, interpolateObject, createContext, parseYaml, stringifyYaml, type WorkoutTemplate, type TemplateParam, type TemplateParams, type TemplateRegistry, type InterpolationContext, } from "./templates/index.js";
|
|
10
|
+
export { expandPlan, expandWorkout, validateWorkoutRefs, calculateHRZones, calculatePaceZones, calculateAthleteZones, parsePace, formatPace, type ExpandedPlan, type ExpandedWeek, type ExpandedDay, type ExpandedWorkout, type ExpandedPhase, type ExpandedAthleteZones, type ExpansionOptions, } from "./expander/index.js";
|
|
7
11
|
export type { Sport, WorkoutType, IntensityUnit, DurationUnit, StepType, SwimDistanceUnit, LandDistanceUnit, FirstDayOfWeek, IntensityTarget, DurationTarget, WorkoutStep, IntervalSet, StructuredWorkout, WeekSummary, HeartRateZones, PowerZones, SwimZones, PaceZones, } from "./schema/training-plan.js";
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Public API for validating and working with training plans.
|
|
5
5
|
*/
|
|
6
|
-
// Schema validation
|
|
6
|
+
// Full Schema validation (v1.0 - expanded format)
|
|
7
7
|
export {
|
|
8
8
|
// Validation functions
|
|
9
9
|
validatePlan, validatePlanOrThrow, formatValidationErrors, getJsonSchema,
|
|
@@ -11,3 +11,11 @@ validatePlan, validatePlanOrThrow, formatValidationErrors, getJsonSchema,
|
|
|
11
11
|
TrainingPlanSchema,
|
|
12
12
|
// Component schemas (for partial validation)
|
|
13
13
|
WorkoutSchema, TrainingWeekSchema, TrainingDaySchema, TrainingPhaseSchema, AthleteAssessmentSchema, AthleteZonesSchema, RaceStrategySchema, UnitPreferencesSchema, PlanMetaSchema, } from "./schema/training-plan.schema.js";
|
|
14
|
+
// Compact Schema validation (v2.0 - template-based format)
|
|
15
|
+
export { validateCompactPlan, validateCompactPlanOrThrow, formatCompactValidationErrors, CompactPlanSchema, CompactAthleteSchema, CompactPhaseSchema, CompactWeekSchema, } from "./schema/compact-plan.schema.js";
|
|
16
|
+
// Compact plan utilities
|
|
17
|
+
export { parseWorkoutRef, parseWeekRange } from "./schema/compact-plan.js";
|
|
18
|
+
// Templates
|
|
19
|
+
export { loadTemplates, loadTemplatesFromArray, getTemplatesPath, validateTemplate, validateTemplateOrThrow, interpolate, interpolateObject, createContext, parseYaml, stringifyYaml, } from "./templates/index.js";
|
|
20
|
+
// Expander
|
|
21
|
+
export { expandPlan, expandWorkout, validateWorkoutRefs, calculateHRZones, calculatePaceZones, calculateAthleteZones, parsePace, formatPace, } from "./expander/index.js";
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compact Training Plan Schema
|
|
3
|
+
*
|
|
4
|
+
* A minimal schema for AI models to generate training plans.
|
|
5
|
+
* The compact format is expanded to the full format for HTML rendering.
|
|
6
|
+
*
|
|
7
|
+
* Key design principles:
|
|
8
|
+
* - Minimal output for models to generate
|
|
9
|
+
* - Template references instead of full workout definitions
|
|
10
|
+
* - Athlete paces/zones as inputs, system calculates ranges
|
|
11
|
+
*/
|
|
12
|
+
export type Sport = "swim" | "bike" | "run" | "strength" | "brick" | "race" | "rest";
|
|
13
|
+
export type FirstDayOfWeek = "monday" | "sunday";
|
|
14
|
+
export type DistanceUnit = "km" | "mi";
|
|
15
|
+
/**
|
|
16
|
+
* Athlete's pace values for different workout intensities.
|
|
17
|
+
* Models specify these once, templates interpolate them.
|
|
18
|
+
*/
|
|
19
|
+
export interface AthletePaces {
|
|
20
|
+
easy?: string;
|
|
21
|
+
long?: string;
|
|
22
|
+
tempo?: string;
|
|
23
|
+
threshold?: string;
|
|
24
|
+
marathon?: string;
|
|
25
|
+
halfMarathon?: string;
|
|
26
|
+
interval?: string;
|
|
27
|
+
r200?: string;
|
|
28
|
+
r400?: string;
|
|
29
|
+
r800?: string;
|
|
30
|
+
r1k?: string;
|
|
31
|
+
rMile?: string;
|
|
32
|
+
bikeFtp?: number;
|
|
33
|
+
bikeEasy?: string;
|
|
34
|
+
bikeTempo?: string;
|
|
35
|
+
bikeThreshold?: string;
|
|
36
|
+
swimCss?: string;
|
|
37
|
+
swimEasy?: string;
|
|
38
|
+
swimTempo?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Heart rate zone configuration.
|
|
42
|
+
* Only LTHR required - zone ranges are calculated automatically.
|
|
43
|
+
*/
|
|
44
|
+
export interface HRZoneConfig {
|
|
45
|
+
lthr: number;
|
|
46
|
+
maxHR?: number;
|
|
47
|
+
restingHR?: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Athlete's training zones.
|
|
51
|
+
* Minimal input, system calculates full zone ranges.
|
|
52
|
+
*/
|
|
53
|
+
export interface AthleteZones {
|
|
54
|
+
hr?: HRZoneConfig;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Athlete preferences and constraints.
|
|
58
|
+
*/
|
|
59
|
+
export interface AthleteConstraints {
|
|
60
|
+
daysPerWeek?: number | string;
|
|
61
|
+
preferredDays?: string[];
|
|
62
|
+
maxLongRunHours?: number;
|
|
63
|
+
maxLongBikeHours?: number;
|
|
64
|
+
notes?: string[];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Complete athlete configuration in compact format.
|
|
68
|
+
*/
|
|
69
|
+
export interface CompactAthlete {
|
|
70
|
+
name: string;
|
|
71
|
+
event: string;
|
|
72
|
+
eventDate: string;
|
|
73
|
+
paces: AthletePaces;
|
|
74
|
+
zones?: AthleteZones;
|
|
75
|
+
constraints?: AthleteConstraints;
|
|
76
|
+
unit?: DistanceUnit;
|
|
77
|
+
firstDayOfWeek?: FirstDayOfWeek;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A training phase defines a block of focused training.
|
|
81
|
+
* Weeks can be a range string or array of week numbers.
|
|
82
|
+
*/
|
|
83
|
+
export interface CompactPhase {
|
|
84
|
+
name: string;
|
|
85
|
+
weeks: string | number[];
|
|
86
|
+
focus: string;
|
|
87
|
+
keyWorkouts?: string[];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* A workout reference is a template ID with optional parameters.
|
|
91
|
+
*
|
|
92
|
+
* Format: "template.id" or "template.id(param)" or "template.id(param1, param2)"
|
|
93
|
+
*
|
|
94
|
+
* Examples:
|
|
95
|
+
* - "easy(30)" → easy run, 30 minutes
|
|
96
|
+
* - "intervals.400(6)" → 6x400m intervals
|
|
97
|
+
* - "long(90)" → 90-minute long run
|
|
98
|
+
* - "rest" → rest day (no params)
|
|
99
|
+
* - "tempo(20)" → 20-minute tempo section
|
|
100
|
+
*/
|
|
101
|
+
export type WorkoutRef = string;
|
|
102
|
+
/**
|
|
103
|
+
* Day of the week as used in the schedule.
|
|
104
|
+
*/
|
|
105
|
+
export type DayOfWeek = "Mon" | "Tue" | "Wed" | "Thu" | "Fri" | "Sat" | "Sun";
|
|
106
|
+
/**
|
|
107
|
+
* A week's workout schedule maps days to workout references.
|
|
108
|
+
* Days without workouts are implicitly rest days.
|
|
109
|
+
*/
|
|
110
|
+
export interface CompactWeekSchedule {
|
|
111
|
+
[day: string]: WorkoutRef | WorkoutRef[];
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* A training week in the compact format.
|
|
115
|
+
*/
|
|
116
|
+
export interface CompactWeek {
|
|
117
|
+
week: number;
|
|
118
|
+
phase: string;
|
|
119
|
+
focus?: string;
|
|
120
|
+
isRecoveryWeek?: boolean;
|
|
121
|
+
targetHours?: number;
|
|
122
|
+
workouts: CompactWeekSchedule;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Simplified race strategy for the compact format.
|
|
126
|
+
*/
|
|
127
|
+
export interface CompactRaceStrategy {
|
|
128
|
+
goalTime?: string;
|
|
129
|
+
pacing?: {
|
|
130
|
+
swim?: string;
|
|
131
|
+
bike?: string;
|
|
132
|
+
run?: string;
|
|
133
|
+
};
|
|
134
|
+
nutrition?: string;
|
|
135
|
+
notes?: string[];
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* The complete compact training plan.
|
|
139
|
+
*
|
|
140
|
+
* This is what AI models generate. It's transformed to the full
|
|
141
|
+
* expanded format for HTML rendering and device export.
|
|
142
|
+
*/
|
|
143
|
+
export interface CompactPlan {
|
|
144
|
+
version: "2.0";
|
|
145
|
+
athlete: CompactAthlete;
|
|
146
|
+
phases: CompactPhase[];
|
|
147
|
+
weeks: CompactWeek[];
|
|
148
|
+
raceStrategy?: CompactRaceStrategy;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Parsed workout reference.
|
|
152
|
+
*/
|
|
153
|
+
export interface ParsedWorkoutRef {
|
|
154
|
+
templateId: string;
|
|
155
|
+
params: (string | number)[];
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Parse a workout reference string into its components.
|
|
159
|
+
*
|
|
160
|
+
* Examples:
|
|
161
|
+
* "easy(30)" → { templateId: "easy", params: [30] }
|
|
162
|
+
* "intervals.400(6)" → { templateId: "intervals.400", params: [6] }
|
|
163
|
+
* "rest" → { templateId: "rest", params: [] }
|
|
164
|
+
* "tempo(20, 90)" → { templateId: "tempo", params: [20, 90] }
|
|
165
|
+
*/
|
|
166
|
+
export declare function parseWorkoutRef(ref: string): ParsedWorkoutRef;
|
|
167
|
+
/**
|
|
168
|
+
* Parse a week range string into an array of week numbers.
|
|
169
|
+
*
|
|
170
|
+
* Examples:
|
|
171
|
+
* "1-3" → [1, 2, 3]
|
|
172
|
+
* "4-6" → [4, 5, 6]
|
|
173
|
+
* [1, 2, 3] → [1, 2, 3] (passthrough)
|
|
174
|
+
*/
|
|
175
|
+
export declare function parseWeekRange(weeks: string | number[]): number[];
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compact Training Plan Schema
|
|
3
|
+
*
|
|
4
|
+
* A minimal schema for AI models to generate training plans.
|
|
5
|
+
* The compact format is expanded to the full format for HTML rendering.
|
|
6
|
+
*
|
|
7
|
+
* Key design principles:
|
|
8
|
+
* - Minimal output for models to generate
|
|
9
|
+
* - Template references instead of full workout definitions
|
|
10
|
+
* - Athlete paces/zones as inputs, system calculates ranges
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Parse a workout reference string into its components.
|
|
14
|
+
*
|
|
15
|
+
* Examples:
|
|
16
|
+
* "easy(30)" → { templateId: "easy", params: [30] }
|
|
17
|
+
* "intervals.400(6)" → { templateId: "intervals.400", params: [6] }
|
|
18
|
+
* "rest" → { templateId: "rest", params: [] }
|
|
19
|
+
* "tempo(20, 90)" → { templateId: "tempo", params: [20, 90] }
|
|
20
|
+
*/
|
|
21
|
+
export function parseWorkoutRef(ref) {
|
|
22
|
+
const match = ref.match(/^([a-zA-Z0-9_.]+)(?:\(([^)]*)\))?$/);
|
|
23
|
+
if (!match) {
|
|
24
|
+
throw new Error(`Invalid workout reference: "${ref}"`);
|
|
25
|
+
}
|
|
26
|
+
const templateId = match[1];
|
|
27
|
+
const paramsStr = match[2];
|
|
28
|
+
let params = [];
|
|
29
|
+
if (paramsStr) {
|
|
30
|
+
params = paramsStr.split(",").map((p) => {
|
|
31
|
+
const trimmed = p.trim();
|
|
32
|
+
const num = Number(trimmed);
|
|
33
|
+
return isNaN(num) ? trimmed : num;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return { templateId, params };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Parse a week range string into an array of week numbers.
|
|
40
|
+
*
|
|
41
|
+
* Examples:
|
|
42
|
+
* "1-3" → [1, 2, 3]
|
|
43
|
+
* "4-6" → [4, 5, 6]
|
|
44
|
+
* [1, 2, 3] → [1, 2, 3] (passthrough)
|
|
45
|
+
*/
|
|
46
|
+
export function parseWeekRange(weeks) {
|
|
47
|
+
if (Array.isArray(weeks)) {
|
|
48
|
+
return weeks;
|
|
49
|
+
}
|
|
50
|
+
const match = weeks.match(/^(\d+)-(\d+)$/);
|
|
51
|
+
if (!match) {
|
|
52
|
+
throw new Error(`Invalid week range: "${weeks}"`);
|
|
53
|
+
}
|
|
54
|
+
const start = parseInt(match[1], 10);
|
|
55
|
+
const end = parseInt(match[2], 10);
|
|
56
|
+
if (start > end) {
|
|
57
|
+
throw new Error(`Invalid week range: start (${start}) > end (${end})`);
|
|
58
|
+
}
|
|
59
|
+
const result = [];
|
|
60
|
+
for (let i = start; i <= end; i++) {
|
|
61
|
+
result.push(i);
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|