@pwf-dev/core 0.1.1

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.
@@ -0,0 +1,3833 @@
1
+ /**
2
+ * Portable Workout Format v1 schema for validating plan documents
3
+ */
4
+ interface PWFPlanV1 {
5
+ /**
6
+ * Specification version (must be 1)
7
+ */
8
+ plan_version: 1;
9
+ meta?: Meta;
10
+ /**
11
+ * Term definitions for exercises and concepts used in this plan
12
+ */
13
+ glossary?: {
14
+ [k: string]: string;
15
+ };
16
+ cycle: Cycle;
17
+ }
18
+ /**
19
+ * Plan metadata for display and organization
20
+ */
21
+ interface Meta {
22
+ /**
23
+ * Unique plan identifier
24
+ */
25
+ id?: string;
26
+ /**
27
+ * Plan display name
28
+ */
29
+ title: string;
30
+ /**
31
+ * Brief plan description
32
+ */
33
+ description?: string;
34
+ /**
35
+ * Coach or creator name
36
+ */
37
+ author?: string;
38
+ /**
39
+ * Plan status
40
+ */
41
+ status?: "draft" | "active" | "completed" | "archived";
42
+ /**
43
+ * ISO 8601 timestamp when plan was activated
44
+ */
45
+ activated_at?: string;
46
+ /**
47
+ * ISO 8601 timestamp when plan was completed
48
+ */
49
+ completed_at?: string;
50
+ /**
51
+ * Required equipment tags
52
+ */
53
+ equipment?: string[];
54
+ /**
55
+ * Intended training frequency
56
+ */
57
+ daysPerWeek?: number;
58
+ /**
59
+ * Suggest as starter plan
60
+ */
61
+ recommendedFirst?: boolean;
62
+ /**
63
+ * Searchable tags
64
+ */
65
+ tags?: string[];
66
+ athlete_profile?: AthleteProfile;
67
+ }
68
+ /**
69
+ * Athlete metrics for endurance training
70
+ */
71
+ interface AthleteProfile {
72
+ /**
73
+ * Functional Threshold Power in watts
74
+ */
75
+ ftp_watts?: number;
76
+ /**
77
+ * Threshold heart rate in BPM
78
+ */
79
+ threshold_hr_bpm?: number;
80
+ /**
81
+ * Maximum heart rate in BPM
82
+ */
83
+ max_hr_bpm?: number;
84
+ /**
85
+ * Threshold pace in seconds per kilometer
86
+ */
87
+ threshold_pace_sec_per_km?: number;
88
+ /**
89
+ * Athlete weight in kilograms
90
+ */
91
+ weight_kg?: number;
92
+ }
93
+ /**
94
+ * Training cycle containing workout days
95
+ */
96
+ interface Cycle {
97
+ /**
98
+ * ISO 8601 date (YYYY-MM-DD)
99
+ */
100
+ start_date?: string;
101
+ /**
102
+ * Cycle-level coaching notes
103
+ */
104
+ notes?: string;
105
+ /**
106
+ * Training days in this cycle
107
+ *
108
+ * @minItems 1
109
+ */
110
+ days: [Day, ...Day[]];
111
+ }
112
+ /**
113
+ * Single training day
114
+ */
115
+ interface Day {
116
+ /**
117
+ * Unique day identifier
118
+ */
119
+ id?: string;
120
+ /**
121
+ * Day sequence (0-indexed)
122
+ */
123
+ order?: number;
124
+ /**
125
+ * Training focus or theme
126
+ */
127
+ focus?: string;
128
+ /**
129
+ * Day-level coaching notes
130
+ */
131
+ notes?: string;
132
+ /**
133
+ * Planned workout date
134
+ */
135
+ scheduled_date?: string;
136
+ /**
137
+ * Expected duration in minutes
138
+ */
139
+ target_session_length_min?: number;
140
+ /**
141
+ * Exercises in this day
142
+ *
143
+ * @minItems 1
144
+ */
145
+ exercises: [Exercise, ...Exercise[]];
146
+ }
147
+ /**
148
+ * Single exercise definition
149
+ */
150
+ interface Exercise {
151
+ /**
152
+ * Unique exercise identifier
153
+ */
154
+ id?: string;
155
+ /**
156
+ * Exercise name
157
+ */
158
+ name?: string;
159
+ /**
160
+ * Exercise type
161
+ */
162
+ modality: "strength" | "countdown" | "stopwatch" | "interval" | "cycling" | "running" | "rowing" | "swimming";
163
+ /**
164
+ * Target number of sets
165
+ */
166
+ target_sets?: number;
167
+ /**
168
+ * Target reps per set
169
+ */
170
+ target_reps?: number;
171
+ /**
172
+ * Target duration in seconds
173
+ */
174
+ target_duration_sec?: number;
175
+ /**
176
+ * Target distance in meters
177
+ */
178
+ target_distance_meters?: number;
179
+ /**
180
+ * Loading guidance (weight, RPE, %1RM)
181
+ */
182
+ target_load?: string;
183
+ /**
184
+ * Target weight as percentage of reference max (requires percent_of)
185
+ */
186
+ target_weight_percent?: number;
187
+ /**
188
+ * Reference max for percentage calculation (requires target_weight_percent)
189
+ */
190
+ percent_of?: "1rm" | "3rm" | "5rm" | "10rm";
191
+ /**
192
+ * Reference another exercise's max for percentage calculation
193
+ */
194
+ reference_exercise?: string;
195
+ /**
196
+ * Form cues (alias for target_notes)
197
+ */
198
+ cues?: string;
199
+ /**
200
+ * Coaching notes for this exercise
201
+ */
202
+ target_notes?: string;
203
+ /**
204
+ * Tutorial URL (HTTPS only)
205
+ */
206
+ link?: string;
207
+ /**
208
+ * Demo image URL (HTTPS only)
209
+ */
210
+ image?: string;
211
+ /**
212
+ * Group identifier for supersets/circuits (alphanumeric, hyphens, underscores only)
213
+ */
214
+ group?: string;
215
+ /**
216
+ * Type of exercise grouping
217
+ */
218
+ group_type?: "superset" | "circuit";
219
+ /**
220
+ * Rest period in seconds between sets
221
+ */
222
+ rest_between_sets_sec?: number;
223
+ /**
224
+ * Rest period in seconds after completing all sets
225
+ */
226
+ rest_after_sec?: number;
227
+ /**
228
+ * Training zones for endurance workouts
229
+ *
230
+ * @minItems 1
231
+ */
232
+ zones?: [TrainingZone, ...TrainingZone[]];
233
+ ramp?: RampConfig;
234
+ /**
235
+ * Structured interval phases for complex endurance workouts
236
+ *
237
+ * @minItems 1
238
+ */
239
+ interval_phases?: [IntervalPhase, ...IntervalPhase[]];
240
+ }
241
+ /**
242
+ * Training zone specification
243
+ */
244
+ interface TrainingZone {
245
+ /**
246
+ * Training zone number (1-7)
247
+ */
248
+ zone: number;
249
+ /**
250
+ * Duration in this zone (seconds)
251
+ */
252
+ duration_sec?: number;
253
+ /**
254
+ * Target power in watts
255
+ */
256
+ target_power_watts?: number;
257
+ /**
258
+ * Target heart rate in BPM
259
+ */
260
+ target_hr_bpm?: number;
261
+ /**
262
+ * Target pace in seconds per kilometer
263
+ */
264
+ target_pace_sec_per_km?: number;
265
+ }
266
+ /**
267
+ * Ramp configuration for gradual intensity changes
268
+ */
269
+ interface RampConfig {
270
+ /**
271
+ * Starting power in watts
272
+ */
273
+ start_power_watts: number;
274
+ /**
275
+ * Ending power in watts
276
+ */
277
+ end_power_watts: number;
278
+ /**
279
+ * Ramp duration in seconds
280
+ */
281
+ duration_sec: number;
282
+ /**
283
+ * Duration of each step in seconds
284
+ */
285
+ step_duration_sec?: number;
286
+ }
287
+ /**
288
+ * Phase in a structured interval workout
289
+ */
290
+ interface IntervalPhase {
291
+ /**
292
+ * Phase name (e.g., 'warmup', 'work', 'recovery')
293
+ */
294
+ name: string;
295
+ /**
296
+ * Phase duration in seconds
297
+ */
298
+ duration_sec: number;
299
+ /**
300
+ * Target power in watts
301
+ */
302
+ target_power_watts?: number;
303
+ /**
304
+ * Target heart rate in BPM
305
+ */
306
+ target_hr_bpm?: number;
307
+ /**
308
+ * Target pace in seconds per kilometer
309
+ */
310
+ target_pace_sec_per_km?: number;
311
+ /**
312
+ * Target cadence in RPM
313
+ */
314
+ cadence_rpm?: number;
315
+ }
316
+
317
+ /**
318
+ * Swimming stroke type for pool and open water swimming
319
+ */
320
+ type StrokeType = "freestyle" | "backstroke" | "breaststroke" | "butterfly" | "drill" | "mixed" | "im";
321
+ /**
322
+ * Schema for validating PWF workout history exports
323
+ */
324
+ interface PWFHistoryExportV1 {
325
+ /**
326
+ * Specification version (must be 1)
327
+ */
328
+ history_version: 1;
329
+ /**
330
+ * ISO 8601 datetime when export was created
331
+ */
332
+ exported_at: string;
333
+ export_source?: ExportSource;
334
+ units?: Units1;
335
+ /**
336
+ * Completed workout sessions
337
+ */
338
+ workouts: Workout[];
339
+ /**
340
+ * Personal records achieved
341
+ */
342
+ personal_records?: PersonalRecord[];
343
+ /**
344
+ * Body measurements recorded
345
+ */
346
+ body_measurements?: BodyMeasurement[];
347
+ }
348
+ interface ExportSource {
349
+ /**
350
+ * Application name
351
+ */
352
+ app_name?: string;
353
+ /**
354
+ * Application version
355
+ */
356
+ app_version?: string;
357
+ /**
358
+ * Platform
359
+ */
360
+ platform?: "ios" | "android" | "web" | "desktop";
361
+ preferred_units?: Units;
362
+ }
363
+ /**
364
+ * User's preferred units
365
+ */
366
+ interface Units {
367
+ weight?: "kg" | "lb";
368
+ distance?: "meters" | "kilometers" | "miles" | "feet" | "yards";
369
+ }
370
+ interface Units1 {
371
+ weight?: "kg" | "lb";
372
+ distance?: "meters" | "kilometers" | "miles" | "feet" | "yards";
373
+ }
374
+ interface Workout {
375
+ /**
376
+ * Unique workout identifier
377
+ */
378
+ id?: string;
379
+ /**
380
+ * Workout date (YYYY-MM-DD)
381
+ */
382
+ date: string;
383
+ /**
384
+ * Start timestamp
385
+ */
386
+ started_at?: string;
387
+ /**
388
+ * End timestamp
389
+ */
390
+ ended_at?: string;
391
+ /**
392
+ * Total duration in seconds
393
+ */
394
+ duration_sec?: number;
395
+ /**
396
+ * Workout title
397
+ */
398
+ title?: string;
399
+ /**
400
+ * Workout notes
401
+ */
402
+ notes?: string;
403
+ /**
404
+ * Reference to PWF plan
405
+ */
406
+ plan_id?: string;
407
+ /**
408
+ * Reference to plan day
409
+ */
410
+ plan_day_id?: string;
411
+ /**
412
+ * Exercises performed
413
+ */
414
+ exercises: CompletedExercise[];
415
+ telemetry?: WorkoutTelemetry;
416
+ /**
417
+ * Devices used during workout (PWF v2)
418
+ */
419
+ devices?: DeviceInfo[];
420
+ /**
421
+ * Primary sport for this workout (PWF v2.1)
422
+ */
423
+ sport?: "swimming" | "cycling" | "running" | "rowing" | "transition" | "strength" | "strength-training" | "hiking" | "walking" | "yoga" | "pilates" | "cross-fit" | "calisthenics" | "cardio" | "cross-country-skiing" | "downhill-skiing" | "snowboarding" | "stand-up-paddling" | "kayaking" | "elliptical" | "stair-climbing" | "other";
424
+ /**
425
+ * Sport segments for multi-sport workouts like triathlon (PWF v2.1)
426
+ */
427
+ sport_segments?: SportSegment[];
428
+ }
429
+ interface CompletedExercise {
430
+ /**
431
+ * Unique exercise identifier
432
+ */
433
+ id?: string;
434
+ /**
435
+ * Exercise name
436
+ */
437
+ name: string;
438
+ /**
439
+ * Exercise modality
440
+ */
441
+ modality?: "strength" | "countdown" | "stopwatch" | "interval" | "swimming";
442
+ /**
443
+ * Exercise-level notes
444
+ */
445
+ notes?: string;
446
+ /**
447
+ * Completed sets
448
+ */
449
+ sets: CompletedSet[];
450
+ pool_config?: PoolConfig;
451
+ /**
452
+ * Sport classification for this exercise (PWF v2.1)
453
+ */
454
+ sport?: "swimming" | "cycling" | "running" | "rowing" | "transition" | "strength" | "strength-training" | "hiking" | "walking" | "yoga" | "pilates" | "cross-fit" | "calisthenics" | "cardio" | "cross-country-skiing" | "downhill-skiing" | "snowboarding" | "stand-up-paddling" | "kayaking" | "elliptical" | "stair-climbing" | "other";
455
+ }
456
+ interface CompletedSet {
457
+ /**
458
+ * Set order (1-indexed)
459
+ */
460
+ set_number?: number;
461
+ /**
462
+ * Type of set
463
+ */
464
+ set_type?: "working" | "warmup" | "dropset" | "failure" | "amrap";
465
+ /**
466
+ * Repetitions completed
467
+ */
468
+ reps?: number;
469
+ /**
470
+ * Weight in kilograms
471
+ */
472
+ weight_kg?: number;
473
+ /**
474
+ * Weight in pounds
475
+ */
476
+ weight_lb?: number;
477
+ /**
478
+ * Duration in seconds
479
+ */
480
+ duration_sec?: number;
481
+ /**
482
+ * Distance in meters
483
+ */
484
+ distance_meters?: number;
485
+ /**
486
+ * Rate of Perceived Exertion (1-10 scale)
487
+ */
488
+ rpe?: number;
489
+ /**
490
+ * Reps in Reserve (alternative to RPE)
491
+ */
492
+ rir?: number;
493
+ /**
494
+ * Set-level notes
495
+ */
496
+ notes?: string;
497
+ /**
498
+ * Whether this set was a personal record
499
+ */
500
+ is_pr?: boolean;
501
+ /**
502
+ * When set was completed
503
+ */
504
+ completed_at?: string;
505
+ telemetry?: SetTelemetry;
506
+ swimming?: SwimmingSetData;
507
+ }
508
+ /**
509
+ * Telemetry metrics for this set (PWF v2)
510
+ */
511
+ interface SetTelemetry {
512
+ /**
513
+ * Average heart rate (bpm)
514
+ */
515
+ heart_rate_avg?: number;
516
+ /**
517
+ * Maximum heart rate (bpm)
518
+ */
519
+ heart_rate_max?: number;
520
+ /**
521
+ * Minimum heart rate (bpm)
522
+ */
523
+ heart_rate_min?: number;
524
+ /**
525
+ * Average power (watts)
526
+ */
527
+ power_avg?: number;
528
+ /**
529
+ * Maximum power (watts)
530
+ */
531
+ power_max?: number;
532
+ /**
533
+ * Minimum power (watts)
534
+ */
535
+ power_min?: number;
536
+ /**
537
+ * Elevation gain in meters
538
+ */
539
+ elevation_gain_m?: number;
540
+ /**
541
+ * Elevation gain in feet
542
+ */
543
+ elevation_gain_ft?: number;
544
+ /**
545
+ * Elevation loss in meters
546
+ */
547
+ elevation_loss_m?: number;
548
+ /**
549
+ * Elevation loss in feet
550
+ */
551
+ elevation_loss_ft?: number;
552
+ /**
553
+ * Average speed in m/s
554
+ */
555
+ speed_avg_mps?: number;
556
+ /**
557
+ * Average speed in km/h
558
+ */
559
+ speed_avg_kph?: number;
560
+ /**
561
+ * Average speed in mph
562
+ */
563
+ speed_avg_mph?: number;
564
+ /**
565
+ * Maximum speed in m/s
566
+ */
567
+ speed_max_mps?: number;
568
+ /**
569
+ * Maximum speed in km/h
570
+ */
571
+ speed_max_kph?: number;
572
+ /**
573
+ * Maximum speed in mph
574
+ */
575
+ speed_max_mph?: number;
576
+ /**
577
+ * Average pace in seconds per km
578
+ */
579
+ pace_avg_sec_per_km?: number;
580
+ /**
581
+ * Average pace in seconds per mile
582
+ */
583
+ pace_avg_sec_per_mi?: number;
584
+ /**
585
+ * Average cadence (RPM or SPM)
586
+ */
587
+ cadence_avg?: number;
588
+ /**
589
+ * Maximum cadence (RPM or SPM)
590
+ */
591
+ cadence_max?: number;
592
+ /**
593
+ * Temperature in Celsius
594
+ */
595
+ temperature_c?: number;
596
+ /**
597
+ * Temperature in Fahrenheit
598
+ */
599
+ temperature_f?: number;
600
+ /**
601
+ * Humidity percentage
602
+ */
603
+ humidity_percent?: number;
604
+ /**
605
+ * Calories burned in this set
606
+ */
607
+ calories?: number;
608
+ /**
609
+ * Stroke rate for swimming/rowing (strokes per minute)
610
+ */
611
+ stroke_rate?: number;
612
+ /**
613
+ * GPS route identifier
614
+ */
615
+ gps_route_id?: string;
616
+ time_series?: TimeSeriesData;
617
+ }
618
+ /**
619
+ * Second-by-second time-series data (PWF v2.1)
620
+ */
621
+ interface TimeSeriesData {
622
+ /**
623
+ * Timestamps for each record (ISO 8601). All other arrays must match this length.
624
+ */
625
+ timestamps: string[];
626
+ /**
627
+ * Elapsed time in seconds since start
628
+ */
629
+ elapsed_sec?: number[];
630
+ /**
631
+ * Heart rate readings (bpm)
632
+ */
633
+ heart_rate?: number[];
634
+ /**
635
+ * Power readings (watts)
636
+ */
637
+ power?: number[];
638
+ /**
639
+ * Cadence readings (RPM for cycling, SPM for running/swimming)
640
+ */
641
+ cadence?: number[];
642
+ /**
643
+ * Speed readings (meters per second)
644
+ */
645
+ speed_mps?: number[];
646
+ /**
647
+ * Distance readings (cumulative meters)
648
+ */
649
+ distance_m?: number[];
650
+ /**
651
+ * Elevation/altitude readings (meters)
652
+ */
653
+ elevation_m?: number[];
654
+ /**
655
+ * Temperature readings (Celsius)
656
+ */
657
+ temperature_c?: number[];
658
+ /**
659
+ * Latitude readings (decimal degrees)
660
+ */
661
+ latitude?: number[];
662
+ /**
663
+ * Longitude readings (decimal degrees)
664
+ */
665
+ longitude?: number[];
666
+ /**
667
+ * Grade/slope readings (percentage)
668
+ */
669
+ grade_percent?: number[];
670
+ /**
671
+ * Respiration rate (breaths per minute)
672
+ */
673
+ respiration_rate?: number[];
674
+ /**
675
+ * Core body temperature (Celsius)
676
+ */
677
+ core_temperature_c?: number[];
678
+ /**
679
+ * Muscle oxygen saturation (percentage)
680
+ */
681
+ muscle_oxygen_percent?: number[];
682
+ /**
683
+ * Left/right power balance (percentage left)
684
+ */
685
+ power_balance?: number[];
686
+ /**
687
+ * Left pedal smoothness (percentage)
688
+ */
689
+ left_pedal_smoothness?: number[];
690
+ /**
691
+ * Right pedal smoothness (percentage)
692
+ */
693
+ right_pedal_smoothness?: number[];
694
+ /**
695
+ * Left torque effectiveness (percentage)
696
+ */
697
+ left_torque_effectiveness?: number[];
698
+ /**
699
+ * Right torque effectiveness (percentage)
700
+ */
701
+ right_torque_effectiveness?: number[];
702
+ /**
703
+ * Running stride length (meters)
704
+ */
705
+ stride_length_m?: number[];
706
+ /**
707
+ * Running vertical oscillation (centimeters)
708
+ */
709
+ vertical_oscillation_cm?: number[];
710
+ /**
711
+ * Running ground contact time (milliseconds)
712
+ */
713
+ ground_contact_time_ms?: number[];
714
+ /**
715
+ * Running ground contact balance (percentage left)
716
+ */
717
+ ground_contact_balance?: number[];
718
+ /**
719
+ * Swimming stroke rate (strokes per minute)
720
+ */
721
+ stroke_rate?: number[];
722
+ /**
723
+ * Swimming stroke count (cumulative)
724
+ */
725
+ stroke_count?: number[];
726
+ /**
727
+ * Swimming SWOLF score
728
+ */
729
+ swolf?: number[];
730
+ /**
731
+ * Swimming stroke type at each point
732
+ */
733
+ stroke_type?: StrokeType[];
734
+ }
735
+ /**
736
+ * Swimming-specific data for this set (PWF v2.1)
737
+ */
738
+ interface SwimmingSetData {
739
+ /**
740
+ * Individual lengths within this set/lap
741
+ */
742
+ lengths?: SwimmingLength[];
743
+ /**
744
+ * Swimming stroke type for pool and open water swimming
745
+ */
746
+ stroke_type?: "freestyle" | "backstroke" | "breaststroke" | "butterfly" | "drill" | "mixed" | "im";
747
+ /**
748
+ * Total number of lengths in this set
749
+ */
750
+ total_lengths?: number;
751
+ /**
752
+ * Number of active lengths (excludes rest at wall)
753
+ */
754
+ active_lengths?: number;
755
+ /**
756
+ * Average SWOLF across all lengths in this set
757
+ */
758
+ swolf_avg?: number;
759
+ /**
760
+ * Whether this set was drill work (technique focus)
761
+ */
762
+ drill_mode?: boolean;
763
+ }
764
+ /**
765
+ * A single length (one pool length) within a swimming set/lap (PWF v2.1)
766
+ */
767
+ interface SwimmingLength {
768
+ /**
769
+ * Length number within the set (1-indexed)
770
+ */
771
+ length_number: number;
772
+ /**
773
+ * Swimming stroke type for pool and open water swimming
774
+ */
775
+ stroke_type: "freestyle" | "backstroke" | "breaststroke" | "butterfly" | "drill" | "mixed" | "im";
776
+ /**
777
+ * Duration of this length in seconds
778
+ */
779
+ duration_sec: number;
780
+ /**
781
+ * Number of strokes taken during this length
782
+ */
783
+ stroke_count?: number;
784
+ /**
785
+ * SWOLF score (duration + stroke_count) - lower is better
786
+ */
787
+ swolf?: number;
788
+ /**
789
+ * Timestamp when this length started (ISO 8601)
790
+ */
791
+ started_at?: string;
792
+ /**
793
+ * Whether this was an active length (vs. rest at wall)
794
+ */
795
+ active?: boolean;
796
+ }
797
+ /**
798
+ * Pool configuration for swimming exercises (PWF v2.1)
799
+ */
800
+ interface PoolConfig {
801
+ /**
802
+ * Length of the pool in the specified units
803
+ */
804
+ pool_length: number;
805
+ /**
806
+ * Unit for pool length (meters or yards)
807
+ */
808
+ pool_length_unit?: "meters" | "yards";
809
+ }
810
+ /**
811
+ * Telemetry metrics for entire workout session (PWF v2)
812
+ */
813
+ interface WorkoutTelemetry {
814
+ /**
815
+ * Average heart rate (bpm)
816
+ */
817
+ heart_rate_avg?: number;
818
+ /**
819
+ * Maximum heart rate (bpm)
820
+ */
821
+ heart_rate_max?: number;
822
+ /**
823
+ * Minimum heart rate (bpm)
824
+ */
825
+ heart_rate_min?: number;
826
+ /**
827
+ * Average power (watts)
828
+ */
829
+ power_avg?: number;
830
+ /**
831
+ * Maximum power (watts)
832
+ */
833
+ power_max?: number;
834
+ /**
835
+ * Total distance in meters
836
+ */
837
+ total_distance_m?: number;
838
+ /**
839
+ * Total distance in kilometers
840
+ */
841
+ total_distance_km?: number;
842
+ /**
843
+ * Total distance in miles
844
+ */
845
+ total_distance_mi?: number;
846
+ /**
847
+ * Total elevation gain in meters
848
+ */
849
+ total_elevation_gain_m?: number;
850
+ /**
851
+ * Total elevation gain in feet
852
+ */
853
+ total_elevation_gain_ft?: number;
854
+ /**
855
+ * Total elevation loss in meters
856
+ */
857
+ total_elevation_loss_m?: number;
858
+ /**
859
+ * Total elevation loss in feet
860
+ */
861
+ total_elevation_loss_ft?: number;
862
+ /**
863
+ * Average speed in km/h
864
+ */
865
+ speed_avg_kph?: number;
866
+ /**
867
+ * Average speed in mph
868
+ */
869
+ speed_avg_mph?: number;
870
+ /**
871
+ * Maximum speed in km/h
872
+ */
873
+ speed_max_kph?: number;
874
+ /**
875
+ * Maximum speed in mph
876
+ */
877
+ speed_max_mph?: number;
878
+ /**
879
+ * Average pace in seconds per km
880
+ */
881
+ pace_avg_sec_per_km?: number;
882
+ /**
883
+ * Average pace in seconds per mile
884
+ */
885
+ pace_avg_sec_per_mi?: number;
886
+ /**
887
+ * Average cadence (RPM or SPM)
888
+ */
889
+ cadence_avg?: number;
890
+ /**
891
+ * Temperature in Celsius
892
+ */
893
+ temperature_c?: number;
894
+ /**
895
+ * Temperature in Fahrenheit
896
+ */
897
+ temperature_f?: number;
898
+ /**
899
+ * Humidity percentage
900
+ */
901
+ humidity_percent?: number;
902
+ /**
903
+ * Total calories burned
904
+ */
905
+ total_calories?: number;
906
+ /**
907
+ * GPS route identifier
908
+ */
909
+ gps_route_id?: string;
910
+ gps_route?: GpsRoute;
911
+ advanced_metrics?: AdvancedMetrics;
912
+ power_metrics?: PowerMetrics;
913
+ time_in_zones?: TimeInZones;
914
+ }
915
+ /**
916
+ * Full GPS route data (PWF v2.1)
917
+ */
918
+ interface GpsRoute {
919
+ /**
920
+ * Unique identifier for this route
921
+ */
922
+ route_id: string;
923
+ /**
924
+ * Human-readable route name
925
+ */
926
+ name?: string;
927
+ /**
928
+ * GPS positions in chronological order
929
+ */
930
+ positions: GpsPosition[];
931
+ /**
932
+ * Total distance calculated from GPS (meters)
933
+ */
934
+ total_distance_m?: number;
935
+ /**
936
+ * Total elevation gain (meters)
937
+ */
938
+ total_ascent_m?: number;
939
+ /**
940
+ * Total elevation loss (meters)
941
+ */
942
+ total_descent_m?: number;
943
+ /**
944
+ * Minimum elevation on route (meters)
945
+ */
946
+ min_elevation_m?: number;
947
+ /**
948
+ * Maximum elevation on route (meters)
949
+ */
950
+ max_elevation_m?: number;
951
+ /**
952
+ * Bounding box - southwest corner latitude
953
+ */
954
+ bbox_sw_lat?: number;
955
+ /**
956
+ * Bounding box - southwest corner longitude
957
+ */
958
+ bbox_sw_lng?: number;
959
+ /**
960
+ * Bounding box - northeast corner latitude
961
+ */
962
+ bbox_ne_lat?: number;
963
+ /**
964
+ * Bounding box - northeast corner longitude
965
+ */
966
+ bbox_ne_lng?: number;
967
+ /**
968
+ * Recording mode (e.g., auto, smart, 1s, gps_only)
969
+ */
970
+ recording_mode?: string;
971
+ /**
972
+ * GPS fix quality indicator
973
+ */
974
+ gps_fix?: "none" | "fix_2d" | "fix_3d" | "dgps" | "unknown";
975
+ }
976
+ /**
977
+ * A single GPS position/waypoint with timestamp (PWF v2.1)
978
+ */
979
+ interface GpsPosition {
980
+ /**
981
+ * Latitude in decimal degrees (WGS84)
982
+ */
983
+ latitude_deg: number;
984
+ /**
985
+ * Longitude in decimal degrees (WGS84)
986
+ */
987
+ longitude_deg: number;
988
+ /**
989
+ * Timestamp when position was recorded (ISO 8601)
990
+ */
991
+ timestamp: string;
992
+ /**
993
+ * Elevation/altitude above sea level (meters)
994
+ */
995
+ elevation_m?: number;
996
+ /**
997
+ * Horizontal accuracy/uncertainty (meters)
998
+ */
999
+ accuracy_m?: number;
1000
+ /**
1001
+ * Speed at this point (meters per second)
1002
+ */
1003
+ speed_mps?: number;
1004
+ /**
1005
+ * Heading/bearing (degrees from north, 0-360)
1006
+ */
1007
+ heading_deg?: number;
1008
+ /**
1009
+ * Heart rate at this position (bpm)
1010
+ */
1011
+ heart_rate_bpm?: number;
1012
+ /**
1013
+ * Power at this position (watts)
1014
+ */
1015
+ power_watts?: number;
1016
+ /**
1017
+ * Cadence at this position (RPM or SPM)
1018
+ */
1019
+ cadence?: number;
1020
+ /**
1021
+ * Temperature at this position (Celsius)
1022
+ */
1023
+ temperature_c?: number;
1024
+ }
1025
+ /**
1026
+ * Advanced physiological metrics (PWF v2.1)
1027
+ */
1028
+ interface AdvancedMetrics {
1029
+ /**
1030
+ * Aerobic Training Effect score (0.0-5.0)
1031
+ */
1032
+ training_effect?: number;
1033
+ /**
1034
+ * Anaerobic Training Effect score (0.0-5.0)
1035
+ */
1036
+ anaerobic_training_effect?: number;
1037
+ /**
1038
+ * Recommended recovery time in hours
1039
+ */
1040
+ recovery_time_hours?: number;
1041
+ /**
1042
+ * VO2 Max estimate in ml/kg/min
1043
+ */
1044
+ vo2_max_estimate?: number;
1045
+ lactate_threshold?: LactateThreshold;
1046
+ /**
1047
+ * Real-time performance assessment (-20 to +20)
1048
+ */
1049
+ performance_condition?: number;
1050
+ /**
1051
+ * Cumulative training stress (0-1000+)
1052
+ */
1053
+ training_load?: number;
1054
+ /**
1055
+ * Training status assessment
1056
+ */
1057
+ training_status?: "detraining" | "recovery" | "maintaining" | "productive" | "peaking" | "overreaching" | "unknown";
1058
+ }
1059
+ /**
1060
+ * Lactate threshold data
1061
+ */
1062
+ interface LactateThreshold {
1063
+ /**
1064
+ * Heart rate at lactate threshold (bpm)
1065
+ */
1066
+ heart_rate_bpm?: number;
1067
+ /**
1068
+ * Speed at lactate threshold (m/s)
1069
+ */
1070
+ speed_mps?: number;
1071
+ /**
1072
+ * Power at lactate threshold (watts, for cycling)
1073
+ */
1074
+ power_watts?: number;
1075
+ /**
1076
+ * When threshold was detected/calculated
1077
+ */
1078
+ detected_at?: string;
1079
+ }
1080
+ /**
1081
+ * Power-based cycling metrics (PWF v2.1)
1082
+ */
1083
+ interface PowerMetrics {
1084
+ /**
1085
+ * Normalized Power (NP) - weighted average accounting for variability
1086
+ */
1087
+ normalized_power?: number;
1088
+ /**
1089
+ * Training Stress Score (TSS) - quantifies training load
1090
+ */
1091
+ training_stress_score?: number;
1092
+ /**
1093
+ * Intensity Factor (IF) - ratio of NP to FTP
1094
+ */
1095
+ intensity_factor?: number;
1096
+ /**
1097
+ * Variability Index (VI) - ratio of NP to average power
1098
+ */
1099
+ variability_index?: number;
1100
+ /**
1101
+ * Functional Threshold Power used for calculations (watts)
1102
+ */
1103
+ ftp_watts?: number;
1104
+ /**
1105
+ * Total work in kilojoules
1106
+ */
1107
+ total_work_kj?: number;
1108
+ /**
1109
+ * Left/right power balance (percentage left)
1110
+ */
1111
+ left_right_balance?: number;
1112
+ /**
1113
+ * Average left pedal smoothness (percentage)
1114
+ */
1115
+ left_pedal_smoothness?: number;
1116
+ /**
1117
+ * Average right pedal smoothness (percentage)
1118
+ */
1119
+ right_pedal_smoothness?: number;
1120
+ /**
1121
+ * Average left torque effectiveness (percentage)
1122
+ */
1123
+ left_torque_effectiveness?: number;
1124
+ /**
1125
+ * Average right torque effectiveness (percentage)
1126
+ */
1127
+ right_torque_effectiveness?: number;
1128
+ }
1129
+ /**
1130
+ * Time in HR/power zones (PWF v2.1)
1131
+ */
1132
+ interface TimeInZones {
1133
+ /**
1134
+ * Time in each HR zone (seconds per zone)
1135
+ */
1136
+ hr_zones_sec?: number[];
1137
+ /**
1138
+ * Time in each power zone (seconds per zone)
1139
+ */
1140
+ power_zones_sec?: number[];
1141
+ /**
1142
+ * HR zone boundaries in bpm
1143
+ */
1144
+ hr_zone_boundaries?: number[];
1145
+ /**
1146
+ * Power zone boundaries in watts
1147
+ */
1148
+ power_zone_boundaries?: number[];
1149
+ /**
1150
+ * Time in each pace zone (seconds per zone)
1151
+ */
1152
+ pace_zones_sec?: number[];
1153
+ /**
1154
+ * Pace zone boundaries (seconds per km)
1155
+ */
1156
+ pace_zone_boundaries?: number[];
1157
+ }
1158
+ /**
1159
+ * Information about a device used during the workout (PWF v2)
1160
+ */
1161
+ interface DeviceInfo {
1162
+ /**
1163
+ * Device index for multi-device workouts (e.g., 0=watch, 1=HRM, 2=power meter)
1164
+ */
1165
+ device_index?: number;
1166
+ /**
1167
+ * Type of device
1168
+ */
1169
+ device_type: "watch" | "bike_computer" | "heart_rate_monitor" | "power_meter" | "speed_sensor" | "cadence_sensor" | "speed_cadence_sensor" | "foot_pod" | "smart_trainer" | "camera" | "phone" | "other";
1170
+ /**
1171
+ * Device manufacturer (can be a known manufacturer or custom string)
1172
+ */
1173
+ manufacturer: string;
1174
+ /**
1175
+ * Specific product/model name
1176
+ */
1177
+ product?: string;
1178
+ /**
1179
+ * Unique device serial number
1180
+ */
1181
+ serial_number?: string;
1182
+ /**
1183
+ * Software/firmware version
1184
+ */
1185
+ software_version?: string;
1186
+ /**
1187
+ * Hardware version
1188
+ */
1189
+ hardware_version?: string;
1190
+ battery?: BatteryInfo;
1191
+ /**
1192
+ * Cumulative operating time in hours
1193
+ */
1194
+ cumulative_operating_time_hours?: number;
1195
+ connection?: ConnectionInfo;
1196
+ calibration?: CalibrationInfo;
1197
+ }
1198
+ /**
1199
+ * Battery information
1200
+ */
1201
+ interface BatteryInfo {
1202
+ /**
1203
+ * Battery level at start of workout (percentage)
1204
+ */
1205
+ start_percent?: number;
1206
+ /**
1207
+ * Battery level at end of workout (percentage)
1208
+ */
1209
+ end_percent?: number;
1210
+ /**
1211
+ * Battery voltage
1212
+ */
1213
+ voltage?: number;
1214
+ /**
1215
+ * Battery status indicator
1216
+ */
1217
+ status?: "good" | "low" | "critical" | "charging" | "unknown";
1218
+ }
1219
+ /**
1220
+ * Connection information for sensors
1221
+ */
1222
+ interface ConnectionInfo {
1223
+ /**
1224
+ * Type of connection
1225
+ */
1226
+ connection_type: "local" | "ant_plus" | "bluetooth_le" | "bluetooth" | "wifi" | "usb" | "unknown";
1227
+ /**
1228
+ * ANT+ device number (for ANT+ sensors)
1229
+ */
1230
+ ant_device_number?: number;
1231
+ /**
1232
+ * Bluetooth MAC address or identifier
1233
+ */
1234
+ bluetooth_id?: string;
1235
+ }
1236
+ /**
1237
+ * Calibration information for sensors
1238
+ */
1239
+ interface CalibrationInfo {
1240
+ /**
1241
+ * Calibration factor or zero offset
1242
+ */
1243
+ calibration_factor?: number;
1244
+ /**
1245
+ * Timestamp of last calibration
1246
+ */
1247
+ last_calibrated?: string;
1248
+ /**
1249
+ * Auto-zero setting (for power meters)
1250
+ */
1251
+ auto_zero_enabled?: boolean;
1252
+ }
1253
+ /**
1254
+ * A segment within a multi-sport workout (PWF v2.1)
1255
+ */
1256
+ interface SportSegment {
1257
+ /**
1258
+ * Segment identifier
1259
+ */
1260
+ segment_id: string;
1261
+ /**
1262
+ * Sport for this segment
1263
+ */
1264
+ sport: "swimming" | "cycling" | "running" | "rowing" | "transition" | "strength" | "strength-training" | "hiking" | "walking" | "yoga" | "pilates" | "cross-fit" | "calisthenics" | "cardio" | "cross-country-skiing" | "downhill-skiing" | "snowboarding" | "stand-up-paddling" | "kayaking" | "elliptical" | "stair-climbing" | "other";
1265
+ /**
1266
+ * Segment number in sequence (0-indexed)
1267
+ */
1268
+ segment_index: number;
1269
+ /**
1270
+ * When segment started (ISO 8601)
1271
+ */
1272
+ started_at?: string;
1273
+ /**
1274
+ * Segment duration in seconds
1275
+ */
1276
+ duration_sec?: number;
1277
+ /**
1278
+ * Distance covered in this segment (meters)
1279
+ */
1280
+ distance_m?: number;
1281
+ /**
1282
+ * Exercises/sets completed during this segment
1283
+ */
1284
+ exercise_ids?: string[];
1285
+ telemetry?: WorkoutTelemetry1;
1286
+ transition?: TransitionData;
1287
+ /**
1288
+ * Notes specific to this segment
1289
+ */
1290
+ notes?: string;
1291
+ }
1292
+ /**
1293
+ * Telemetry specific to this segment
1294
+ */
1295
+ interface WorkoutTelemetry1 {
1296
+ /**
1297
+ * Average heart rate (bpm)
1298
+ */
1299
+ heart_rate_avg?: number;
1300
+ /**
1301
+ * Maximum heart rate (bpm)
1302
+ */
1303
+ heart_rate_max?: number;
1304
+ /**
1305
+ * Minimum heart rate (bpm)
1306
+ */
1307
+ heart_rate_min?: number;
1308
+ /**
1309
+ * Average power (watts)
1310
+ */
1311
+ power_avg?: number;
1312
+ /**
1313
+ * Maximum power (watts)
1314
+ */
1315
+ power_max?: number;
1316
+ /**
1317
+ * Total distance in meters
1318
+ */
1319
+ total_distance_m?: number;
1320
+ /**
1321
+ * Total distance in kilometers
1322
+ */
1323
+ total_distance_km?: number;
1324
+ /**
1325
+ * Total distance in miles
1326
+ */
1327
+ total_distance_mi?: number;
1328
+ /**
1329
+ * Total elevation gain in meters
1330
+ */
1331
+ total_elevation_gain_m?: number;
1332
+ /**
1333
+ * Total elevation gain in feet
1334
+ */
1335
+ total_elevation_gain_ft?: number;
1336
+ /**
1337
+ * Total elevation loss in meters
1338
+ */
1339
+ total_elevation_loss_m?: number;
1340
+ /**
1341
+ * Total elevation loss in feet
1342
+ */
1343
+ total_elevation_loss_ft?: number;
1344
+ /**
1345
+ * Average speed in km/h
1346
+ */
1347
+ speed_avg_kph?: number;
1348
+ /**
1349
+ * Average speed in mph
1350
+ */
1351
+ speed_avg_mph?: number;
1352
+ /**
1353
+ * Maximum speed in km/h
1354
+ */
1355
+ speed_max_kph?: number;
1356
+ /**
1357
+ * Maximum speed in mph
1358
+ */
1359
+ speed_max_mph?: number;
1360
+ /**
1361
+ * Average pace in seconds per km
1362
+ */
1363
+ pace_avg_sec_per_km?: number;
1364
+ /**
1365
+ * Average pace in seconds per mile
1366
+ */
1367
+ pace_avg_sec_per_mi?: number;
1368
+ /**
1369
+ * Average cadence (RPM or SPM)
1370
+ */
1371
+ cadence_avg?: number;
1372
+ /**
1373
+ * Temperature in Celsius
1374
+ */
1375
+ temperature_c?: number;
1376
+ /**
1377
+ * Temperature in Fahrenheit
1378
+ */
1379
+ temperature_f?: number;
1380
+ /**
1381
+ * Humidity percentage
1382
+ */
1383
+ humidity_percent?: number;
1384
+ /**
1385
+ * Total calories burned
1386
+ */
1387
+ total_calories?: number;
1388
+ /**
1389
+ * GPS route identifier
1390
+ */
1391
+ gps_route_id?: string;
1392
+ gps_route?: GpsRoute;
1393
+ advanced_metrics?: AdvancedMetrics;
1394
+ power_metrics?: PowerMetrics;
1395
+ time_in_zones?: TimeInZones;
1396
+ }
1397
+ /**
1398
+ * Transition data after this segment
1399
+ */
1400
+ interface TransitionData {
1401
+ /**
1402
+ * Transition identifier (e.g., T1, T2)
1403
+ */
1404
+ transition_id: string;
1405
+ /**
1406
+ * From which sport
1407
+ */
1408
+ from_sport: "swimming" | "cycling" | "running" | "rowing" | "transition" | "strength" | "strength-training" | "hiking" | "walking" | "yoga" | "pilates" | "cross-fit" | "calisthenics" | "cardio" | "cross-country-skiing" | "downhill-skiing" | "snowboarding" | "stand-up-paddling" | "kayaking" | "elliptical" | "stair-climbing" | "other";
1409
+ /**
1410
+ * To which sport
1411
+ */
1412
+ to_sport: "swimming" | "cycling" | "running" | "rowing" | "transition" | "strength" | "strength-training" | "hiking" | "walking" | "yoga" | "pilates" | "cross-fit" | "calisthenics" | "cardio" | "cross-country-skiing" | "downhill-skiing" | "snowboarding" | "stand-up-paddling" | "kayaking" | "elliptical" | "stair-climbing" | "other";
1413
+ /**
1414
+ * Transition duration in seconds
1415
+ */
1416
+ duration_sec?: number;
1417
+ /**
1418
+ * When transition started (ISO 8601)
1419
+ */
1420
+ started_at?: string;
1421
+ /**
1422
+ * Average heart rate during transition
1423
+ */
1424
+ heart_rate_avg?: number;
1425
+ /**
1426
+ * Notes about transition (e.g., equipment changes)
1427
+ */
1428
+ notes?: string;
1429
+ }
1430
+ interface PersonalRecord {
1431
+ /**
1432
+ * Exercise name
1433
+ */
1434
+ exercise_name: string;
1435
+ /**
1436
+ * Type of record
1437
+ */
1438
+ record_type: "1rm" | "max_weight_3rm" | "max_weight_5rm" | "max_weight_8rm" | "max_weight_10rm" | "max_weight" | "max_reps" | "max_volume" | "max_duration" | "max_distance" | "fastest_time";
1439
+ /**
1440
+ * Record value
1441
+ */
1442
+ value: number;
1443
+ /**
1444
+ * Unit for the value
1445
+ */
1446
+ unit?: string;
1447
+ /**
1448
+ * Date achieved
1449
+ */
1450
+ achieved_at: string;
1451
+ /**
1452
+ * Reference to workout
1453
+ */
1454
+ workout_id?: string;
1455
+ /**
1456
+ * Additional notes
1457
+ */
1458
+ notes?: string;
1459
+ }
1460
+ interface BodyMeasurement {
1461
+ /**
1462
+ * Measurement date
1463
+ */
1464
+ date: string;
1465
+ /**
1466
+ * Exact timestamp
1467
+ */
1468
+ recorded_at?: string;
1469
+ /**
1470
+ * Body weight in kg
1471
+ */
1472
+ weight_kg?: number;
1473
+ /**
1474
+ * Body weight in lb
1475
+ */
1476
+ weight_lb?: number;
1477
+ /**
1478
+ * Body fat percentage
1479
+ */
1480
+ body_fat_percent?: number;
1481
+ /**
1482
+ * Notes
1483
+ */
1484
+ notes?: string;
1485
+ measurements?: BodyDimensions;
1486
+ }
1487
+ interface BodyDimensions {
1488
+ neck_cm?: number;
1489
+ shoulders_cm?: number;
1490
+ chest_cm?: number;
1491
+ waist_cm?: number;
1492
+ hips_cm?: number;
1493
+ bicep_left_cm?: number;
1494
+ bicep_right_cm?: number;
1495
+ forearm_left_cm?: number;
1496
+ forearm_right_cm?: number;
1497
+ thigh_left_cm?: number;
1498
+ thigh_right_cm?: number;
1499
+ calf_left_cm?: number;
1500
+ calf_right_cm?: number;
1501
+ }
1502
+
1503
+ type Severity = 'error' | 'warning';
1504
+ interface ValidationIssue {
1505
+ path: string;
1506
+ message: string;
1507
+ severity: Severity;
1508
+ code?: string;
1509
+ }
1510
+ type ValidationError = ValidationIssue;
1511
+ type ValidationWarning = ValidationIssue;
1512
+ interface ValidationOptions {
1513
+ strict?: boolean;
1514
+ }
1515
+ interface ParseOptions extends ValidationOptions {
1516
+ }
1517
+ type PlanMeta = Meta;
1518
+ type PlanExercise = Exercise;
1519
+ type PlanDay = Day;
1520
+ type PlanCycle = Cycle;
1521
+ type PwfPlan = PWFPlanV1;
1522
+ type Modality = PlanExercise['modality'];
1523
+ interface PlanDocument extends PwfPlan {
1524
+ toYAML(): string;
1525
+ }
1526
+ type HistoryExercise = CompletedExercise;
1527
+ type HistoryWorkout = Workout;
1528
+ type PwfHistory = PWFHistoryExportV1;
1529
+
1530
+ declare function parsePlan(text: string, options?: ParseOptions): PwfPlan | ValidationIssue[];
1531
+ declare function parseHistory(text: string, options?: ParseOptions): PwfHistory | ValidationIssue[];
1532
+ declare function isValidationIssueList(value: unknown): value is ValidationIssue[];
1533
+ declare function toYAML(value: unknown): string;
1534
+ declare function fromYAML<T>(text: string): T;
1535
+
1536
+ declare function validatePlan(plan: unknown, _options?: ValidationOptions): ValidationIssue[];
1537
+ declare function validateHistory(history: unknown, _options?: ValidationOptions): ValidationIssue[];
1538
+
1539
+ type PlanDayDraft = Omit<NonNullable<PwfPlan['cycle']>['days'][number], 'exercises'> & {
1540
+ exercises?: PlanExercise[];
1541
+ };
1542
+ declare class PlanBuilder {
1543
+ private plan;
1544
+ private currentDayIndex;
1545
+ constructor();
1546
+ version(version: PwfPlan['plan_version']): this;
1547
+ meta(meta: PlanMeta): this;
1548
+ glossary(glossary: Record<string, string>): this;
1549
+ addDay(focus?: string, dayOverrides?: Partial<PlanDayDraft>): this;
1550
+ addExercise(name: string, exercise: Omit<PlanExercise, 'name'>): this;
1551
+ toYAML(): string;
1552
+ build(): PlanDocument;
1553
+ }
1554
+
1555
+ var $schema$1 = "http://json-schema.org/draft-07/schema#";
1556
+ var $id$1 = "https://pwf.dev/schema/v1/plan.json";
1557
+ var title$1 = "PWF Plan v1";
1558
+ var description$1 = "Portable Workout Format v1 schema for validating plan documents";
1559
+ var type$1 = "object";
1560
+ var required$1 = [
1561
+ "plan_version",
1562
+ "cycle"
1563
+ ];
1564
+ var additionalProperties$1 = false;
1565
+ var properties$1 = {
1566
+ plan_version: {
1567
+ type: "integer",
1568
+ "const": 1,
1569
+ description: "Specification version (must be 1)"
1570
+ },
1571
+ meta: {
1572
+ $ref: "#/$defs/Meta"
1573
+ },
1574
+ glossary: {
1575
+ type: "object",
1576
+ description: "Term definitions for exercises and concepts used in this plan",
1577
+ maxProperties: 100,
1578
+ additionalProperties: {
1579
+ type: "string",
1580
+ minLength: 1,
1581
+ maxLength: 500
1582
+ },
1583
+ propertyNames: {
1584
+ pattern: "^[a-zA-Z0-9\\s\\-']+$",
1585
+ minLength: 1,
1586
+ maxLength: 50
1587
+ }
1588
+ },
1589
+ cycle: {
1590
+ $ref: "#/$defs/Cycle"
1591
+ }
1592
+ };
1593
+ var $defs$1 = {
1594
+ Meta: {
1595
+ type: "object",
1596
+ description: "Plan metadata for display and organization",
1597
+ additionalProperties: false,
1598
+ required: [
1599
+ "title"
1600
+ ],
1601
+ properties: {
1602
+ id: {
1603
+ type: "string",
1604
+ description: "Unique plan identifier"
1605
+ },
1606
+ title: {
1607
+ type: "string",
1608
+ maxLength: 80,
1609
+ minLength: 1,
1610
+ description: "Plan display name"
1611
+ },
1612
+ description: {
1613
+ type: "string",
1614
+ description: "Brief plan description"
1615
+ },
1616
+ author: {
1617
+ type: "string",
1618
+ description: "Coach or creator name"
1619
+ },
1620
+ status: {
1621
+ type: "string",
1622
+ "enum": [
1623
+ "draft",
1624
+ "active",
1625
+ "completed",
1626
+ "archived"
1627
+ ],
1628
+ "default": "draft",
1629
+ description: "Plan status"
1630
+ },
1631
+ activated_at: {
1632
+ type: "string",
1633
+ format: "date-time",
1634
+ pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(Z|[+-]\\d{2}:\\d{2})$",
1635
+ description: "ISO 8601 timestamp when plan was activated"
1636
+ },
1637
+ completed_at: {
1638
+ type: "string",
1639
+ format: "date-time",
1640
+ pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(Z|[+-]\\d{2}:\\d{2})$",
1641
+ description: "ISO 8601 timestamp when plan was completed"
1642
+ },
1643
+ equipment: {
1644
+ type: "array",
1645
+ items: {
1646
+ type: "string"
1647
+ },
1648
+ uniqueItems: true,
1649
+ description: "Required equipment tags"
1650
+ },
1651
+ daysPerWeek: {
1652
+ type: "integer",
1653
+ minimum: 1,
1654
+ maximum: 7,
1655
+ description: "Intended training frequency"
1656
+ },
1657
+ recommendedFirst: {
1658
+ type: "boolean",
1659
+ "default": false,
1660
+ description: "Suggest as starter plan"
1661
+ },
1662
+ tags: {
1663
+ type: "array",
1664
+ items: {
1665
+ type: "string"
1666
+ },
1667
+ description: "Searchable tags"
1668
+ },
1669
+ athlete_profile: {
1670
+ $ref: "#/$defs/AthleteProfile"
1671
+ }
1672
+ }
1673
+ },
1674
+ AthleteProfile: {
1675
+ type: "object",
1676
+ description: "Athlete metrics for endurance training",
1677
+ additionalProperties: false,
1678
+ properties: {
1679
+ ftp_watts: {
1680
+ type: "integer",
1681
+ minimum: 0,
1682
+ description: "Functional Threshold Power in watts"
1683
+ },
1684
+ threshold_hr_bpm: {
1685
+ type: "integer",
1686
+ minimum: 0,
1687
+ maximum: 250,
1688
+ description: "Threshold heart rate in BPM"
1689
+ },
1690
+ max_hr_bpm: {
1691
+ type: "integer",
1692
+ minimum: 0,
1693
+ maximum: 250,
1694
+ description: "Maximum heart rate in BPM"
1695
+ },
1696
+ threshold_pace_sec_per_km: {
1697
+ type: "integer",
1698
+ minimum: 0,
1699
+ description: "Threshold pace in seconds per kilometer"
1700
+ },
1701
+ weight_kg: {
1702
+ type: "number",
1703
+ minimum: 0,
1704
+ description: "Athlete weight in kilograms"
1705
+ }
1706
+ }
1707
+ },
1708
+ Cycle: {
1709
+ type: "object",
1710
+ description: "Training cycle containing workout days",
1711
+ additionalProperties: false,
1712
+ required: [
1713
+ "days"
1714
+ ],
1715
+ properties: {
1716
+ start_date: {
1717
+ type: "string",
1718
+ format: "date",
1719
+ pattern: "^\\d{4}-\\d{2}-\\d{2}$",
1720
+ description: "ISO 8601 date (YYYY-MM-DD)"
1721
+ },
1722
+ notes: {
1723
+ type: "string",
1724
+ description: "Cycle-level coaching notes"
1725
+ },
1726
+ days: {
1727
+ type: "array",
1728
+ minItems: 1,
1729
+ items: {
1730
+ $ref: "#/$defs/Day"
1731
+ },
1732
+ description: "Training days in this cycle"
1733
+ }
1734
+ }
1735
+ },
1736
+ Day: {
1737
+ type: "object",
1738
+ description: "Single training day",
1739
+ additionalProperties: false,
1740
+ required: [
1741
+ "exercises"
1742
+ ],
1743
+ properties: {
1744
+ id: {
1745
+ type: "string",
1746
+ description: "Unique day identifier"
1747
+ },
1748
+ order: {
1749
+ type: "integer",
1750
+ minimum: 0,
1751
+ description: "Day sequence (0-indexed)"
1752
+ },
1753
+ focus: {
1754
+ type: "string",
1755
+ description: "Training focus or theme"
1756
+ },
1757
+ notes: {
1758
+ type: "string",
1759
+ description: "Day-level coaching notes"
1760
+ },
1761
+ scheduled_date: {
1762
+ type: "string",
1763
+ format: "date",
1764
+ pattern: "^\\d{4}-\\d{2}-\\d{2}$",
1765
+ description: "Planned workout date"
1766
+ },
1767
+ target_session_length_min: {
1768
+ type: "integer",
1769
+ minimum: 1,
1770
+ description: "Expected duration in minutes"
1771
+ },
1772
+ exercises: {
1773
+ type: "array",
1774
+ minItems: 1,
1775
+ items: {
1776
+ $ref: "#/$defs/Exercise"
1777
+ },
1778
+ description: "Exercises in this day"
1779
+ }
1780
+ }
1781
+ },
1782
+ Exercise: {
1783
+ type: "object",
1784
+ description: "Single exercise definition",
1785
+ additionalProperties: false,
1786
+ required: [
1787
+ "modality"
1788
+ ],
1789
+ properties: {
1790
+ id: {
1791
+ type: "string",
1792
+ description: "Unique exercise identifier"
1793
+ },
1794
+ name: {
1795
+ type: "string",
1796
+ description: "Exercise name"
1797
+ },
1798
+ modality: {
1799
+ type: "string",
1800
+ "enum": [
1801
+ "strength",
1802
+ "countdown",
1803
+ "stopwatch",
1804
+ "interval",
1805
+ "cycling",
1806
+ "running",
1807
+ "rowing",
1808
+ "swimming"
1809
+ ],
1810
+ description: "Exercise type"
1811
+ },
1812
+ target_sets: {
1813
+ type: "integer",
1814
+ minimum: 1,
1815
+ description: "Target number of sets"
1816
+ },
1817
+ target_reps: {
1818
+ type: "integer",
1819
+ minimum: 1,
1820
+ description: "Target reps per set"
1821
+ },
1822
+ target_duration_sec: {
1823
+ type: "integer",
1824
+ minimum: 1,
1825
+ description: "Target duration in seconds"
1826
+ },
1827
+ target_distance_meters: {
1828
+ type: "number",
1829
+ minimum: 0,
1830
+ description: "Target distance in meters"
1831
+ },
1832
+ target_load: {
1833
+ type: "string",
1834
+ description: "Loading guidance (weight, RPE, %1RM)"
1835
+ },
1836
+ target_weight_percent: {
1837
+ type: "number",
1838
+ minimum: 0,
1839
+ maximum: 200,
1840
+ description: "Target weight as percentage of reference max (requires percent_of)"
1841
+ },
1842
+ percent_of: {
1843
+ type: "string",
1844
+ "enum": [
1845
+ "1rm",
1846
+ "3rm",
1847
+ "5rm",
1848
+ "10rm"
1849
+ ],
1850
+ description: "Reference max for percentage calculation (requires target_weight_percent)"
1851
+ },
1852
+ reference_exercise: {
1853
+ type: "string",
1854
+ description: "Reference another exercise's max for percentage calculation"
1855
+ },
1856
+ cues: {
1857
+ type: "string",
1858
+ description: "Form cues (alias for target_notes)"
1859
+ },
1860
+ target_notes: {
1861
+ type: "string",
1862
+ description: "Coaching notes for this exercise"
1863
+ },
1864
+ link: {
1865
+ type: "string",
1866
+ format: "uri",
1867
+ pattern: "^https://",
1868
+ description: "Tutorial URL (HTTPS only)"
1869
+ },
1870
+ image: {
1871
+ type: "string",
1872
+ format: "uri",
1873
+ pattern: "^https://",
1874
+ description: "Demo image URL (HTTPS only)"
1875
+ },
1876
+ group: {
1877
+ type: "string",
1878
+ minLength: 1,
1879
+ maxLength: 50,
1880
+ pattern: "^[a-zA-Z0-9_-]+$",
1881
+ description: "Group identifier for supersets/circuits (alphanumeric, hyphens, underscores only)"
1882
+ },
1883
+ group_type: {
1884
+ type: "string",
1885
+ "enum": [
1886
+ "superset",
1887
+ "circuit"
1888
+ ],
1889
+ description: "Type of exercise grouping"
1890
+ },
1891
+ rest_between_sets_sec: {
1892
+ type: "integer",
1893
+ minimum: 0,
1894
+ description: "Rest period in seconds between sets"
1895
+ },
1896
+ rest_after_sec: {
1897
+ type: "integer",
1898
+ minimum: 0,
1899
+ description: "Rest period in seconds after completing all sets"
1900
+ },
1901
+ zones: {
1902
+ type: "array",
1903
+ minItems: 1,
1904
+ items: {
1905
+ $ref: "#/$defs/TrainingZone"
1906
+ },
1907
+ description: "Training zones for endurance workouts"
1908
+ },
1909
+ ramp: {
1910
+ $ref: "#/$defs/RampConfig"
1911
+ },
1912
+ interval_phases: {
1913
+ type: "array",
1914
+ minItems: 1,
1915
+ items: {
1916
+ $ref: "#/$defs/IntervalPhase"
1917
+ },
1918
+ description: "Structured interval phases for complex endurance workouts"
1919
+ }
1920
+ }
1921
+ },
1922
+ TrainingZone: {
1923
+ type: "object",
1924
+ description: "Training zone specification",
1925
+ additionalProperties: false,
1926
+ required: [
1927
+ "zone"
1928
+ ],
1929
+ properties: {
1930
+ zone: {
1931
+ type: "integer",
1932
+ minimum: 1,
1933
+ maximum: 7,
1934
+ description: "Training zone number (1-7)"
1935
+ },
1936
+ duration_sec: {
1937
+ type: "integer",
1938
+ minimum: 0,
1939
+ description: "Duration in this zone (seconds)"
1940
+ },
1941
+ target_power_watts: {
1942
+ type: "integer",
1943
+ minimum: 0,
1944
+ description: "Target power in watts"
1945
+ },
1946
+ target_hr_bpm: {
1947
+ type: "integer",
1948
+ minimum: 0,
1949
+ maximum: 250,
1950
+ description: "Target heart rate in BPM"
1951
+ },
1952
+ target_pace_sec_per_km: {
1953
+ type: "integer",
1954
+ minimum: 0,
1955
+ description: "Target pace in seconds per kilometer"
1956
+ }
1957
+ }
1958
+ },
1959
+ RampConfig: {
1960
+ type: "object",
1961
+ description: "Ramp configuration for gradual intensity changes",
1962
+ additionalProperties: false,
1963
+ required: [
1964
+ "start_power_watts",
1965
+ "end_power_watts",
1966
+ "duration_sec"
1967
+ ],
1968
+ properties: {
1969
+ start_power_watts: {
1970
+ type: "integer",
1971
+ minimum: 0,
1972
+ description: "Starting power in watts"
1973
+ },
1974
+ end_power_watts: {
1975
+ type: "integer",
1976
+ minimum: 0,
1977
+ description: "Ending power in watts"
1978
+ },
1979
+ duration_sec: {
1980
+ type: "integer",
1981
+ minimum: 1,
1982
+ description: "Ramp duration in seconds"
1983
+ },
1984
+ step_duration_sec: {
1985
+ type: "integer",
1986
+ minimum: 1,
1987
+ description: "Duration of each step in seconds"
1988
+ }
1989
+ }
1990
+ },
1991
+ IntervalPhase: {
1992
+ type: "object",
1993
+ description: "Phase in a structured interval workout",
1994
+ additionalProperties: false,
1995
+ required: [
1996
+ "name",
1997
+ "duration_sec"
1998
+ ],
1999
+ properties: {
2000
+ name: {
2001
+ type: "string",
2002
+ minLength: 1,
2003
+ description: "Phase name (e.g., 'warmup', 'work', 'recovery')"
2004
+ },
2005
+ duration_sec: {
2006
+ type: "integer",
2007
+ minimum: 1,
2008
+ description: "Phase duration in seconds"
2009
+ },
2010
+ target_power_watts: {
2011
+ type: "integer",
2012
+ minimum: 0,
2013
+ description: "Target power in watts"
2014
+ },
2015
+ target_hr_bpm: {
2016
+ type: "integer",
2017
+ minimum: 0,
2018
+ maximum: 250,
2019
+ description: "Target heart rate in BPM"
2020
+ },
2021
+ target_pace_sec_per_km: {
2022
+ type: "integer",
2023
+ minimum: 0,
2024
+ description: "Target pace in seconds per kilometer"
2025
+ },
2026
+ cadence_rpm: {
2027
+ type: "integer",
2028
+ minimum: 0,
2029
+ description: "Target cadence in RPM"
2030
+ }
2031
+ }
2032
+ }
2033
+ };
2034
+ var pwfV1 = {
2035
+ $schema: $schema$1,
2036
+ $id: $id$1,
2037
+ title: title$1,
2038
+ description: description$1,
2039
+ type: type$1,
2040
+ required: required$1,
2041
+ additionalProperties: additionalProperties$1,
2042
+ properties: properties$1,
2043
+ $defs: $defs$1
2044
+ };
2045
+
2046
+ var $schema = "http://json-schema.org/draft-07/schema#";
2047
+ var $id = "https://pwf.dev/schema/v1/history.json";
2048
+ var title = "PWF History Export v1";
2049
+ var description = "Schema for validating PWF workout history exports";
2050
+ var type = "object";
2051
+ var required = [
2052
+ "history_version",
2053
+ "exported_at",
2054
+ "workouts"
2055
+ ];
2056
+ var additionalProperties = false;
2057
+ var properties = {
2058
+ history_version: {
2059
+ type: "integer",
2060
+ "const": 1,
2061
+ description: "Specification version (must be 1)"
2062
+ },
2063
+ exported_at: {
2064
+ type: "string",
2065
+ format: "date-time",
2066
+ description: "ISO 8601 datetime when export was created"
2067
+ },
2068
+ export_source: {
2069
+ $ref: "#/$defs/ExportSource"
2070
+ },
2071
+ units: {
2072
+ $ref: "#/$defs/Units"
2073
+ },
2074
+ workouts: {
2075
+ type: "array",
2076
+ items: {
2077
+ $ref: "#/$defs/Workout"
2078
+ },
2079
+ description: "Completed workout sessions"
2080
+ },
2081
+ personal_records: {
2082
+ type: "array",
2083
+ items: {
2084
+ $ref: "#/$defs/PersonalRecord"
2085
+ },
2086
+ description: "Personal records achieved"
2087
+ },
2088
+ body_measurements: {
2089
+ type: "array",
2090
+ items: {
2091
+ $ref: "#/$defs/BodyMeasurement"
2092
+ },
2093
+ description: "Body measurements recorded"
2094
+ }
2095
+ };
2096
+ var $defs = {
2097
+ ExportSource: {
2098
+ type: "object",
2099
+ additionalProperties: false,
2100
+ properties: {
2101
+ app_name: {
2102
+ type: "string",
2103
+ description: "Application name"
2104
+ },
2105
+ app_version: {
2106
+ type: "string",
2107
+ description: "Application version"
2108
+ },
2109
+ platform: {
2110
+ type: "string",
2111
+ "enum": [
2112
+ "ios",
2113
+ "android",
2114
+ "web",
2115
+ "desktop"
2116
+ ],
2117
+ description: "Platform"
2118
+ },
2119
+ preferred_units: {
2120
+ $ref: "#/$defs/Units",
2121
+ description: "User's preferred units"
2122
+ }
2123
+ }
2124
+ },
2125
+ Units: {
2126
+ type: "object",
2127
+ additionalProperties: false,
2128
+ properties: {
2129
+ weight: {
2130
+ type: "string",
2131
+ "enum": [
2132
+ "kg",
2133
+ "lb"
2134
+ ],
2135
+ "default": "kg"
2136
+ },
2137
+ distance: {
2138
+ type: "string",
2139
+ "enum": [
2140
+ "meters",
2141
+ "kilometers",
2142
+ "miles",
2143
+ "feet",
2144
+ "yards"
2145
+ ],
2146
+ "default": "meters"
2147
+ }
2148
+ }
2149
+ },
2150
+ Workout: {
2151
+ type: "object",
2152
+ required: [
2153
+ "date",
2154
+ "exercises"
2155
+ ],
2156
+ additionalProperties: false,
2157
+ properties: {
2158
+ id: {
2159
+ type: "string",
2160
+ description: "Unique workout identifier"
2161
+ },
2162
+ date: {
2163
+ type: "string",
2164
+ format: "date",
2165
+ description: "Workout date (YYYY-MM-DD)"
2166
+ },
2167
+ started_at: {
2168
+ type: "string",
2169
+ format: "date-time",
2170
+ description: "Start timestamp"
2171
+ },
2172
+ ended_at: {
2173
+ type: "string",
2174
+ format: "date-time",
2175
+ description: "End timestamp"
2176
+ },
2177
+ duration_sec: {
2178
+ type: "integer",
2179
+ minimum: 0,
2180
+ description: "Total duration in seconds"
2181
+ },
2182
+ title: {
2183
+ type: "string",
2184
+ description: "Workout title"
2185
+ },
2186
+ notes: {
2187
+ type: "string",
2188
+ description: "Workout notes"
2189
+ },
2190
+ plan_id: {
2191
+ type: "string",
2192
+ description: "Reference to PWF plan"
2193
+ },
2194
+ plan_day_id: {
2195
+ type: "string",
2196
+ description: "Reference to plan day"
2197
+ },
2198
+ exercises: {
2199
+ type: "array",
2200
+ items: {
2201
+ $ref: "#/$defs/CompletedExercise"
2202
+ },
2203
+ description: "Exercises performed"
2204
+ },
2205
+ telemetry: {
2206
+ $ref: "#/$defs/WorkoutTelemetry",
2207
+ description: "Telemetry metrics for entire workout session (PWF v2)"
2208
+ },
2209
+ devices: {
2210
+ type: "array",
2211
+ items: {
2212
+ $ref: "#/$defs/DeviceInfo"
2213
+ },
2214
+ description: "Devices used during workout (PWF v2)"
2215
+ },
2216
+ sport: {
2217
+ $ref: "#/$defs/Sport",
2218
+ description: "Primary sport for this workout (PWF v2.1)"
2219
+ },
2220
+ sport_segments: {
2221
+ type: "array",
2222
+ items: {
2223
+ $ref: "#/$defs/SportSegment"
2224
+ },
2225
+ description: "Sport segments for multi-sport workouts like triathlon (PWF v2.1)"
2226
+ }
2227
+ }
2228
+ },
2229
+ CompletedExercise: {
2230
+ type: "object",
2231
+ required: [
2232
+ "name",
2233
+ "sets"
2234
+ ],
2235
+ additionalProperties: false,
2236
+ properties: {
2237
+ id: {
2238
+ type: "string",
2239
+ description: "Unique exercise identifier"
2240
+ },
2241
+ name: {
2242
+ type: "string",
2243
+ minLength: 1,
2244
+ description: "Exercise name"
2245
+ },
2246
+ modality: {
2247
+ type: "string",
2248
+ "enum": [
2249
+ "strength",
2250
+ "countdown",
2251
+ "stopwatch",
2252
+ "interval",
2253
+ "swimming"
2254
+ ],
2255
+ description: "Exercise modality"
2256
+ },
2257
+ notes: {
2258
+ type: "string",
2259
+ description: "Exercise-level notes"
2260
+ },
2261
+ sets: {
2262
+ type: "array",
2263
+ items: {
2264
+ $ref: "#/$defs/CompletedSet"
2265
+ },
2266
+ description: "Completed sets"
2267
+ },
2268
+ pool_config: {
2269
+ $ref: "#/$defs/PoolConfig",
2270
+ description: "Pool configuration for swimming exercises (PWF v2.1)"
2271
+ },
2272
+ sport: {
2273
+ $ref: "#/$defs/Sport",
2274
+ description: "Sport classification for this exercise (PWF v2.1)"
2275
+ }
2276
+ }
2277
+ },
2278
+ CompletedSet: {
2279
+ type: "object",
2280
+ additionalProperties: false,
2281
+ properties: {
2282
+ set_number: {
2283
+ type: "integer",
2284
+ minimum: 1,
2285
+ description: "Set order (1-indexed)"
2286
+ },
2287
+ set_type: {
2288
+ type: "string",
2289
+ "enum": [
2290
+ "working",
2291
+ "warmup",
2292
+ "dropset",
2293
+ "failure",
2294
+ "amrap"
2295
+ ],
2296
+ "default": "working",
2297
+ description: "Type of set"
2298
+ },
2299
+ reps: {
2300
+ type: "integer",
2301
+ minimum: 0,
2302
+ description: "Repetitions completed"
2303
+ },
2304
+ weight_kg: {
2305
+ type: "number",
2306
+ minimum: 0,
2307
+ description: "Weight in kilograms"
2308
+ },
2309
+ weight_lb: {
2310
+ type: "number",
2311
+ minimum: 0,
2312
+ description: "Weight in pounds"
2313
+ },
2314
+ duration_sec: {
2315
+ type: "integer",
2316
+ minimum: 0,
2317
+ description: "Duration in seconds"
2318
+ },
2319
+ distance_meters: {
2320
+ type: "number",
2321
+ minimum: 0,
2322
+ description: "Distance in meters"
2323
+ },
2324
+ rpe: {
2325
+ type: "number",
2326
+ minimum: 0,
2327
+ maximum: 10,
2328
+ description: "Rate of Perceived Exertion (1-10 scale)"
2329
+ },
2330
+ rir: {
2331
+ type: "integer",
2332
+ minimum: 0,
2333
+ maximum: 10,
2334
+ description: "Reps in Reserve (alternative to RPE)"
2335
+ },
2336
+ notes: {
2337
+ type: "string",
2338
+ description: "Set-level notes"
2339
+ },
2340
+ is_pr: {
2341
+ type: "boolean",
2342
+ description: "Whether this set was a personal record"
2343
+ },
2344
+ completed_at: {
2345
+ type: "string",
2346
+ format: "date-time",
2347
+ description: "When set was completed"
2348
+ },
2349
+ telemetry: {
2350
+ $ref: "#/$defs/SetTelemetry",
2351
+ description: "Telemetry metrics for this set (PWF v2)"
2352
+ },
2353
+ swimming: {
2354
+ $ref: "#/$defs/SwimmingSetData",
2355
+ description: "Swimming-specific data for this set (PWF v2.1)"
2356
+ }
2357
+ }
2358
+ },
2359
+ PersonalRecord: {
2360
+ type: "object",
2361
+ required: [
2362
+ "exercise_name",
2363
+ "record_type",
2364
+ "value",
2365
+ "achieved_at"
2366
+ ],
2367
+ additionalProperties: false,
2368
+ properties: {
2369
+ exercise_name: {
2370
+ type: "string",
2371
+ minLength: 1,
2372
+ description: "Exercise name"
2373
+ },
2374
+ record_type: {
2375
+ type: "string",
2376
+ "enum": [
2377
+ "1rm",
2378
+ "max_weight_3rm",
2379
+ "max_weight_5rm",
2380
+ "max_weight_8rm",
2381
+ "max_weight_10rm",
2382
+ "max_weight",
2383
+ "max_reps",
2384
+ "max_volume",
2385
+ "max_duration",
2386
+ "max_distance",
2387
+ "fastest_time"
2388
+ ],
2389
+ description: "Type of record"
2390
+ },
2391
+ value: {
2392
+ type: "number",
2393
+ description: "Record value"
2394
+ },
2395
+ unit: {
2396
+ type: "string",
2397
+ description: "Unit for the value"
2398
+ },
2399
+ achieved_at: {
2400
+ type: "string",
2401
+ format: "date",
2402
+ description: "Date achieved"
2403
+ },
2404
+ workout_id: {
2405
+ type: "string",
2406
+ description: "Reference to workout"
2407
+ },
2408
+ notes: {
2409
+ type: "string",
2410
+ description: "Additional notes"
2411
+ }
2412
+ }
2413
+ },
2414
+ BodyMeasurement: {
2415
+ type: "object",
2416
+ required: [
2417
+ "date"
2418
+ ],
2419
+ additionalProperties: false,
2420
+ properties: {
2421
+ date: {
2422
+ type: "string",
2423
+ format: "date",
2424
+ description: "Measurement date"
2425
+ },
2426
+ recorded_at: {
2427
+ type: "string",
2428
+ format: "date-time",
2429
+ description: "Exact timestamp"
2430
+ },
2431
+ weight_kg: {
2432
+ type: "number",
2433
+ minimum: 0,
2434
+ description: "Body weight in kg"
2435
+ },
2436
+ weight_lb: {
2437
+ type: "number",
2438
+ minimum: 0,
2439
+ description: "Body weight in lb"
2440
+ },
2441
+ body_fat_percent: {
2442
+ type: "number",
2443
+ minimum: 0,
2444
+ maximum: 100,
2445
+ description: "Body fat percentage"
2446
+ },
2447
+ notes: {
2448
+ type: "string",
2449
+ description: "Notes"
2450
+ },
2451
+ measurements: {
2452
+ $ref: "#/$defs/BodyDimensions"
2453
+ }
2454
+ }
2455
+ },
2456
+ BodyDimensions: {
2457
+ type: "object",
2458
+ additionalProperties: false,
2459
+ properties: {
2460
+ neck_cm: {
2461
+ type: "number",
2462
+ minimum: 0
2463
+ },
2464
+ shoulders_cm: {
2465
+ type: "number",
2466
+ minimum: 0
2467
+ },
2468
+ chest_cm: {
2469
+ type: "number",
2470
+ minimum: 0
2471
+ },
2472
+ waist_cm: {
2473
+ type: "number",
2474
+ minimum: 0
2475
+ },
2476
+ hips_cm: {
2477
+ type: "number",
2478
+ minimum: 0
2479
+ },
2480
+ bicep_left_cm: {
2481
+ type: "number",
2482
+ minimum: 0
2483
+ },
2484
+ bicep_right_cm: {
2485
+ type: "number",
2486
+ minimum: 0
2487
+ },
2488
+ forearm_left_cm: {
2489
+ type: "number",
2490
+ minimum: 0
2491
+ },
2492
+ forearm_right_cm: {
2493
+ type: "number",
2494
+ minimum: 0
2495
+ },
2496
+ thigh_left_cm: {
2497
+ type: "number",
2498
+ minimum: 0
2499
+ },
2500
+ thigh_right_cm: {
2501
+ type: "number",
2502
+ minimum: 0
2503
+ },
2504
+ calf_left_cm: {
2505
+ type: "number",
2506
+ minimum: 0
2507
+ },
2508
+ calf_right_cm: {
2509
+ type: "number",
2510
+ minimum: 0
2511
+ }
2512
+ }
2513
+ },
2514
+ Sport: {
2515
+ type: "string",
2516
+ "enum": [
2517
+ "swimming",
2518
+ "cycling",
2519
+ "running",
2520
+ "rowing",
2521
+ "transition",
2522
+ "strength",
2523
+ "strength-training",
2524
+ "hiking",
2525
+ "walking",
2526
+ "yoga",
2527
+ "pilates",
2528
+ "cross-fit",
2529
+ "calisthenics",
2530
+ "cardio",
2531
+ "cross-country-skiing",
2532
+ "downhill-skiing",
2533
+ "snowboarding",
2534
+ "stand-up-paddling",
2535
+ "kayaking",
2536
+ "elliptical",
2537
+ "stair-climbing",
2538
+ "other"
2539
+ ],
2540
+ description: "Sport classification (PWF v2.1)"
2541
+ },
2542
+ WorkoutTelemetry: {
2543
+ type: "object",
2544
+ additionalProperties: false,
2545
+ description: "Telemetry metrics for entire workout session (PWF v2)",
2546
+ properties: {
2547
+ heart_rate_avg: {
2548
+ type: "integer",
2549
+ minimum: 0,
2550
+ description: "Average heart rate (bpm)"
2551
+ },
2552
+ heart_rate_max: {
2553
+ type: "integer",
2554
+ minimum: 0,
2555
+ description: "Maximum heart rate (bpm)"
2556
+ },
2557
+ heart_rate_min: {
2558
+ type: "integer",
2559
+ minimum: 0,
2560
+ description: "Minimum heart rate (bpm)"
2561
+ },
2562
+ power_avg: {
2563
+ type: "integer",
2564
+ minimum: 0,
2565
+ description: "Average power (watts)"
2566
+ },
2567
+ power_max: {
2568
+ type: "integer",
2569
+ minimum: 0,
2570
+ description: "Maximum power (watts)"
2571
+ },
2572
+ total_distance_m: {
2573
+ type: "number",
2574
+ minimum: 0,
2575
+ description: "Total distance in meters"
2576
+ },
2577
+ total_distance_km: {
2578
+ type: "number",
2579
+ minimum: 0,
2580
+ description: "Total distance in kilometers"
2581
+ },
2582
+ total_distance_mi: {
2583
+ type: "number",
2584
+ minimum: 0,
2585
+ description: "Total distance in miles"
2586
+ },
2587
+ total_elevation_gain_m: {
2588
+ type: "number",
2589
+ description: "Total elevation gain in meters"
2590
+ },
2591
+ total_elevation_gain_ft: {
2592
+ type: "number",
2593
+ description: "Total elevation gain in feet"
2594
+ },
2595
+ total_elevation_loss_m: {
2596
+ type: "number",
2597
+ description: "Total elevation loss in meters"
2598
+ },
2599
+ total_elevation_loss_ft: {
2600
+ type: "number",
2601
+ description: "Total elevation loss in feet"
2602
+ },
2603
+ speed_avg_kph: {
2604
+ type: "number",
2605
+ minimum: 0,
2606
+ description: "Average speed in km/h"
2607
+ },
2608
+ speed_avg_mph: {
2609
+ type: "number",
2610
+ minimum: 0,
2611
+ description: "Average speed in mph"
2612
+ },
2613
+ speed_max_kph: {
2614
+ type: "number",
2615
+ minimum: 0,
2616
+ description: "Maximum speed in km/h"
2617
+ },
2618
+ speed_max_mph: {
2619
+ type: "number",
2620
+ minimum: 0,
2621
+ description: "Maximum speed in mph"
2622
+ },
2623
+ pace_avg_sec_per_km: {
2624
+ type: "integer",
2625
+ minimum: 0,
2626
+ description: "Average pace in seconds per km"
2627
+ },
2628
+ pace_avg_sec_per_mi: {
2629
+ type: "integer",
2630
+ minimum: 0,
2631
+ description: "Average pace in seconds per mile"
2632
+ },
2633
+ cadence_avg: {
2634
+ type: "integer",
2635
+ minimum: 0,
2636
+ description: "Average cadence (RPM or SPM)"
2637
+ },
2638
+ temperature_c: {
2639
+ type: "number",
2640
+ description: "Temperature in Celsius"
2641
+ },
2642
+ temperature_f: {
2643
+ type: "number",
2644
+ description: "Temperature in Fahrenheit"
2645
+ },
2646
+ humidity_percent: {
2647
+ type: "number",
2648
+ minimum: 0,
2649
+ maximum: 100,
2650
+ description: "Humidity percentage"
2651
+ },
2652
+ total_calories: {
2653
+ type: "integer",
2654
+ minimum: 0,
2655
+ description: "Total calories burned"
2656
+ },
2657
+ gps_route_id: {
2658
+ type: "string",
2659
+ description: "GPS route identifier"
2660
+ },
2661
+ gps_route: {
2662
+ $ref: "#/$defs/GpsRoute",
2663
+ description: "Full GPS route data (PWF v2.1)"
2664
+ },
2665
+ advanced_metrics: {
2666
+ $ref: "#/$defs/AdvancedMetrics",
2667
+ description: "Advanced physiological metrics (PWF v2.1)"
2668
+ },
2669
+ power_metrics: {
2670
+ $ref: "#/$defs/PowerMetrics",
2671
+ description: "Power-based cycling metrics (PWF v2.1)"
2672
+ },
2673
+ time_in_zones: {
2674
+ $ref: "#/$defs/TimeInZones",
2675
+ description: "Time in HR/power zones (PWF v2.1)"
2676
+ }
2677
+ }
2678
+ },
2679
+ SetTelemetry: {
2680
+ type: "object",
2681
+ additionalProperties: false,
2682
+ description: "Telemetry metrics for a completed set (PWF v2)",
2683
+ properties: {
2684
+ heart_rate_avg: {
2685
+ type: "integer",
2686
+ minimum: 0,
2687
+ description: "Average heart rate (bpm)"
2688
+ },
2689
+ heart_rate_max: {
2690
+ type: "integer",
2691
+ minimum: 0,
2692
+ description: "Maximum heart rate (bpm)"
2693
+ },
2694
+ heart_rate_min: {
2695
+ type: "integer",
2696
+ minimum: 0,
2697
+ description: "Minimum heart rate (bpm)"
2698
+ },
2699
+ power_avg: {
2700
+ type: "integer",
2701
+ minimum: 0,
2702
+ description: "Average power (watts)"
2703
+ },
2704
+ power_max: {
2705
+ type: "integer",
2706
+ minimum: 0,
2707
+ description: "Maximum power (watts)"
2708
+ },
2709
+ power_min: {
2710
+ type: "integer",
2711
+ minimum: 0,
2712
+ description: "Minimum power (watts)"
2713
+ },
2714
+ elevation_gain_m: {
2715
+ type: "number",
2716
+ description: "Elevation gain in meters"
2717
+ },
2718
+ elevation_gain_ft: {
2719
+ type: "number",
2720
+ description: "Elevation gain in feet"
2721
+ },
2722
+ elevation_loss_m: {
2723
+ type: "number",
2724
+ description: "Elevation loss in meters"
2725
+ },
2726
+ elevation_loss_ft: {
2727
+ type: "number",
2728
+ description: "Elevation loss in feet"
2729
+ },
2730
+ speed_avg_mps: {
2731
+ type: "number",
2732
+ minimum: 0,
2733
+ description: "Average speed in m/s"
2734
+ },
2735
+ speed_avg_kph: {
2736
+ type: "number",
2737
+ minimum: 0,
2738
+ description: "Average speed in km/h"
2739
+ },
2740
+ speed_avg_mph: {
2741
+ type: "number",
2742
+ minimum: 0,
2743
+ description: "Average speed in mph"
2744
+ },
2745
+ speed_max_mps: {
2746
+ type: "number",
2747
+ minimum: 0,
2748
+ description: "Maximum speed in m/s"
2749
+ },
2750
+ speed_max_kph: {
2751
+ type: "number",
2752
+ minimum: 0,
2753
+ description: "Maximum speed in km/h"
2754
+ },
2755
+ speed_max_mph: {
2756
+ type: "number",
2757
+ minimum: 0,
2758
+ description: "Maximum speed in mph"
2759
+ },
2760
+ pace_avg_sec_per_km: {
2761
+ type: "integer",
2762
+ minimum: 0,
2763
+ description: "Average pace in seconds per km"
2764
+ },
2765
+ pace_avg_sec_per_mi: {
2766
+ type: "integer",
2767
+ minimum: 0,
2768
+ description: "Average pace in seconds per mile"
2769
+ },
2770
+ cadence_avg: {
2771
+ type: "integer",
2772
+ minimum: 0,
2773
+ description: "Average cadence (RPM or SPM)"
2774
+ },
2775
+ cadence_max: {
2776
+ type: "integer",
2777
+ minimum: 0,
2778
+ description: "Maximum cadence (RPM or SPM)"
2779
+ },
2780
+ temperature_c: {
2781
+ type: "number",
2782
+ description: "Temperature in Celsius"
2783
+ },
2784
+ temperature_f: {
2785
+ type: "number",
2786
+ description: "Temperature in Fahrenheit"
2787
+ },
2788
+ humidity_percent: {
2789
+ type: "number",
2790
+ minimum: 0,
2791
+ maximum: 100,
2792
+ description: "Humidity percentage"
2793
+ },
2794
+ calories: {
2795
+ type: "integer",
2796
+ minimum: 0,
2797
+ description: "Calories burned in this set"
2798
+ },
2799
+ stroke_rate: {
2800
+ type: "integer",
2801
+ minimum: 0,
2802
+ description: "Stroke rate for swimming/rowing (strokes per minute)"
2803
+ },
2804
+ gps_route_id: {
2805
+ type: "string",
2806
+ description: "GPS route identifier"
2807
+ },
2808
+ time_series: {
2809
+ $ref: "#/$defs/TimeSeriesData",
2810
+ description: "Second-by-second time-series data (PWF v2.1)"
2811
+ }
2812
+ }
2813
+ },
2814
+ AdvancedMetrics: {
2815
+ type: "object",
2816
+ additionalProperties: false,
2817
+ description: "Advanced physiological and performance metrics (PWF v2.1)",
2818
+ properties: {
2819
+ training_effect: {
2820
+ type: "number",
2821
+ minimum: 0,
2822
+ maximum: 5,
2823
+ description: "Aerobic Training Effect score (0.0-5.0)"
2824
+ },
2825
+ anaerobic_training_effect: {
2826
+ type: "number",
2827
+ minimum: 0,
2828
+ maximum: 5,
2829
+ description: "Anaerobic Training Effect score (0.0-5.0)"
2830
+ },
2831
+ recovery_time_hours: {
2832
+ type: "integer",
2833
+ minimum: 0,
2834
+ description: "Recommended recovery time in hours"
2835
+ },
2836
+ vo2_max_estimate: {
2837
+ type: "number",
2838
+ minimum: 0,
2839
+ description: "VO2 Max estimate in ml/kg/min"
2840
+ },
2841
+ lactate_threshold: {
2842
+ $ref: "#/$defs/LactateThreshold",
2843
+ description: "Lactate threshold data"
2844
+ },
2845
+ performance_condition: {
2846
+ type: "integer",
2847
+ minimum: -20,
2848
+ maximum: 20,
2849
+ description: "Real-time performance assessment (-20 to +20)"
2850
+ },
2851
+ training_load: {
2852
+ type: "integer",
2853
+ minimum: 0,
2854
+ description: "Cumulative training stress (0-1000+)"
2855
+ },
2856
+ training_status: {
2857
+ $ref: "#/$defs/TrainingStatus",
2858
+ description: "Training status assessment"
2859
+ }
2860
+ }
2861
+ },
2862
+ LactateThreshold: {
2863
+ type: "object",
2864
+ additionalProperties: false,
2865
+ description: "Lactate threshold tracking",
2866
+ properties: {
2867
+ heart_rate_bpm: {
2868
+ type: "integer",
2869
+ minimum: 0,
2870
+ description: "Heart rate at lactate threshold (bpm)"
2871
+ },
2872
+ speed_mps: {
2873
+ type: "number",
2874
+ minimum: 0,
2875
+ description: "Speed at lactate threshold (m/s)"
2876
+ },
2877
+ power_watts: {
2878
+ type: "integer",
2879
+ minimum: 0,
2880
+ description: "Power at lactate threshold (watts, for cycling)"
2881
+ },
2882
+ detected_at: {
2883
+ type: "string",
2884
+ format: "date-time",
2885
+ description: "When threshold was detected/calculated"
2886
+ }
2887
+ }
2888
+ },
2889
+ TrainingStatus: {
2890
+ type: "string",
2891
+ "enum": [
2892
+ "detraining",
2893
+ "recovery",
2894
+ "maintaining",
2895
+ "productive",
2896
+ "peaking",
2897
+ "overreaching",
2898
+ "unknown"
2899
+ ],
2900
+ description: "Training status classification"
2901
+ },
2902
+ PowerMetrics: {
2903
+ type: "object",
2904
+ additionalProperties: false,
2905
+ description: "Power-based cycling metrics (PWF v2.1)",
2906
+ properties: {
2907
+ normalized_power: {
2908
+ type: "integer",
2909
+ minimum: 0,
2910
+ description: "Normalized Power (NP) - weighted average accounting for variability"
2911
+ },
2912
+ training_stress_score: {
2913
+ type: "number",
2914
+ minimum: 0,
2915
+ description: "Training Stress Score (TSS) - quantifies training load"
2916
+ },
2917
+ intensity_factor: {
2918
+ type: "number",
2919
+ minimum: 0,
2920
+ description: "Intensity Factor (IF) - ratio of NP to FTP"
2921
+ },
2922
+ variability_index: {
2923
+ type: "number",
2924
+ minimum: 0,
2925
+ description: "Variability Index (VI) - ratio of NP to average power"
2926
+ },
2927
+ ftp_watts: {
2928
+ type: "integer",
2929
+ minimum: 0,
2930
+ description: "Functional Threshold Power used for calculations (watts)"
2931
+ },
2932
+ total_work_kj: {
2933
+ type: "number",
2934
+ minimum: 0,
2935
+ description: "Total work in kilojoules"
2936
+ },
2937
+ left_right_balance: {
2938
+ type: "number",
2939
+ minimum: 0,
2940
+ maximum: 100,
2941
+ description: "Left/right power balance (percentage left)"
2942
+ },
2943
+ left_pedal_smoothness: {
2944
+ type: "number",
2945
+ minimum: 0,
2946
+ maximum: 100,
2947
+ description: "Average left pedal smoothness (percentage)"
2948
+ },
2949
+ right_pedal_smoothness: {
2950
+ type: "number",
2951
+ minimum: 0,
2952
+ maximum: 100,
2953
+ description: "Average right pedal smoothness (percentage)"
2954
+ },
2955
+ left_torque_effectiveness: {
2956
+ type: "number",
2957
+ minimum: 0,
2958
+ maximum: 100,
2959
+ description: "Average left torque effectiveness (percentage)"
2960
+ },
2961
+ right_torque_effectiveness: {
2962
+ type: "number",
2963
+ minimum: 0,
2964
+ maximum: 100,
2965
+ description: "Average right torque effectiveness (percentage)"
2966
+ }
2967
+ }
2968
+ },
2969
+ TimeInZones: {
2970
+ type: "object",
2971
+ additionalProperties: false,
2972
+ description: "Time spent in heart rate and power zones (PWF v2.1)",
2973
+ properties: {
2974
+ hr_zones_sec: {
2975
+ type: "array",
2976
+ items: {
2977
+ type: "integer",
2978
+ minimum: 0
2979
+ },
2980
+ description: "Time in each HR zone (seconds per zone)"
2981
+ },
2982
+ power_zones_sec: {
2983
+ type: "array",
2984
+ items: {
2985
+ type: "integer",
2986
+ minimum: 0
2987
+ },
2988
+ description: "Time in each power zone (seconds per zone)"
2989
+ },
2990
+ hr_zone_boundaries: {
2991
+ type: "array",
2992
+ items: {
2993
+ type: "integer",
2994
+ minimum: 0
2995
+ },
2996
+ description: "HR zone boundaries in bpm"
2997
+ },
2998
+ power_zone_boundaries: {
2999
+ type: "array",
3000
+ items: {
3001
+ type: "integer",
3002
+ minimum: 0
3003
+ },
3004
+ description: "Power zone boundaries in watts"
3005
+ },
3006
+ pace_zones_sec: {
3007
+ type: "array",
3008
+ items: {
3009
+ type: "integer",
3010
+ minimum: 0
3011
+ },
3012
+ description: "Time in each pace zone (seconds per zone)"
3013
+ },
3014
+ pace_zone_boundaries: {
3015
+ type: "array",
3016
+ items: {
3017
+ type: "integer",
3018
+ minimum: 0
3019
+ },
3020
+ description: "Pace zone boundaries (seconds per km)"
3021
+ }
3022
+ }
3023
+ },
3024
+ SportSegment: {
3025
+ type: "object",
3026
+ required: [
3027
+ "segment_id",
3028
+ "sport",
3029
+ "segment_index"
3030
+ ],
3031
+ additionalProperties: false,
3032
+ description: "A segment within a multi-sport workout (PWF v2.1)",
3033
+ properties: {
3034
+ segment_id: {
3035
+ type: "string",
3036
+ description: "Segment identifier"
3037
+ },
3038
+ sport: {
3039
+ $ref: "#/$defs/Sport",
3040
+ description: "Sport for this segment"
3041
+ },
3042
+ segment_index: {
3043
+ type: "integer",
3044
+ minimum: 0,
3045
+ description: "Segment number in sequence (0-indexed)"
3046
+ },
3047
+ started_at: {
3048
+ type: "string",
3049
+ format: "date-time",
3050
+ description: "When segment started (ISO 8601)"
3051
+ },
3052
+ duration_sec: {
3053
+ type: "integer",
3054
+ minimum: 0,
3055
+ description: "Segment duration in seconds"
3056
+ },
3057
+ distance_m: {
3058
+ type: "number",
3059
+ minimum: 0,
3060
+ description: "Distance covered in this segment (meters)"
3061
+ },
3062
+ exercise_ids: {
3063
+ type: "array",
3064
+ items: {
3065
+ type: "string"
3066
+ },
3067
+ description: "Exercises/sets completed during this segment"
3068
+ },
3069
+ telemetry: {
3070
+ $ref: "#/$defs/WorkoutTelemetry",
3071
+ description: "Telemetry specific to this segment"
3072
+ },
3073
+ transition: {
3074
+ $ref: "#/$defs/TransitionData",
3075
+ description: "Transition data after this segment"
3076
+ },
3077
+ notes: {
3078
+ type: "string",
3079
+ description: "Notes specific to this segment"
3080
+ }
3081
+ }
3082
+ },
3083
+ TransitionData: {
3084
+ type: "object",
3085
+ required: [
3086
+ "transition_id",
3087
+ "from_sport",
3088
+ "to_sport"
3089
+ ],
3090
+ additionalProperties: false,
3091
+ description: "Transition between sports in a multi-sport event (PWF v2.1)",
3092
+ properties: {
3093
+ transition_id: {
3094
+ type: "string",
3095
+ description: "Transition identifier (e.g., T1, T2)"
3096
+ },
3097
+ from_sport: {
3098
+ $ref: "#/$defs/Sport",
3099
+ description: "From which sport"
3100
+ },
3101
+ to_sport: {
3102
+ $ref: "#/$defs/Sport",
3103
+ description: "To which sport"
3104
+ },
3105
+ duration_sec: {
3106
+ type: "integer",
3107
+ minimum: 0,
3108
+ description: "Transition duration in seconds"
3109
+ },
3110
+ started_at: {
3111
+ type: "string",
3112
+ format: "date-time",
3113
+ description: "When transition started (ISO 8601)"
3114
+ },
3115
+ heart_rate_avg: {
3116
+ type: "integer",
3117
+ minimum: 0,
3118
+ description: "Average heart rate during transition"
3119
+ },
3120
+ notes: {
3121
+ type: "string",
3122
+ description: "Notes about transition (e.g., equipment changes)"
3123
+ }
3124
+ }
3125
+ },
3126
+ GpsRoute: {
3127
+ type: "object",
3128
+ required: [
3129
+ "route_id",
3130
+ "positions"
3131
+ ],
3132
+ additionalProperties: false,
3133
+ description: "A GPS route/track containing multiple positions (PWF v2.1)",
3134
+ properties: {
3135
+ route_id: {
3136
+ type: "string",
3137
+ description: "Unique identifier for this route"
3138
+ },
3139
+ name: {
3140
+ type: "string",
3141
+ description: "Human-readable route name"
3142
+ },
3143
+ positions: {
3144
+ type: "array",
3145
+ items: {
3146
+ $ref: "#/$defs/GpsPosition"
3147
+ },
3148
+ description: "GPS positions in chronological order"
3149
+ },
3150
+ total_distance_m: {
3151
+ type: "number",
3152
+ minimum: 0,
3153
+ description: "Total distance calculated from GPS (meters)"
3154
+ },
3155
+ total_ascent_m: {
3156
+ type: "number",
3157
+ minimum: 0,
3158
+ description: "Total elevation gain (meters)"
3159
+ },
3160
+ total_descent_m: {
3161
+ type: "number",
3162
+ minimum: 0,
3163
+ description: "Total elevation loss (meters)"
3164
+ },
3165
+ min_elevation_m: {
3166
+ type: "number",
3167
+ description: "Minimum elevation on route (meters)"
3168
+ },
3169
+ max_elevation_m: {
3170
+ type: "number",
3171
+ description: "Maximum elevation on route (meters)"
3172
+ },
3173
+ bbox_sw_lat: {
3174
+ type: "number",
3175
+ minimum: -90,
3176
+ maximum: 90,
3177
+ description: "Bounding box - southwest corner latitude"
3178
+ },
3179
+ bbox_sw_lng: {
3180
+ type: "number",
3181
+ minimum: -180,
3182
+ maximum: 180,
3183
+ description: "Bounding box - southwest corner longitude"
3184
+ },
3185
+ bbox_ne_lat: {
3186
+ type: "number",
3187
+ minimum: -90,
3188
+ maximum: 90,
3189
+ description: "Bounding box - northeast corner latitude"
3190
+ },
3191
+ bbox_ne_lng: {
3192
+ type: "number",
3193
+ minimum: -180,
3194
+ maximum: 180,
3195
+ description: "Bounding box - northeast corner longitude"
3196
+ },
3197
+ recording_mode: {
3198
+ type: "string",
3199
+ description: "Recording mode (e.g., auto, smart, 1s, gps_only)"
3200
+ },
3201
+ gps_fix: {
3202
+ $ref: "#/$defs/GpsFix",
3203
+ description: "GPS fix quality indicator"
3204
+ }
3205
+ }
3206
+ },
3207
+ GpsPosition: {
3208
+ type: "object",
3209
+ required: [
3210
+ "latitude_deg",
3211
+ "longitude_deg",
3212
+ "timestamp"
3213
+ ],
3214
+ additionalProperties: false,
3215
+ description: "A single GPS position/waypoint with timestamp (PWF v2.1)",
3216
+ properties: {
3217
+ latitude_deg: {
3218
+ type: "number",
3219
+ minimum: -90,
3220
+ maximum: 90,
3221
+ description: "Latitude in decimal degrees (WGS84)"
3222
+ },
3223
+ longitude_deg: {
3224
+ type: "number",
3225
+ minimum: -180,
3226
+ maximum: 180,
3227
+ description: "Longitude in decimal degrees (WGS84)"
3228
+ },
3229
+ timestamp: {
3230
+ type: "string",
3231
+ format: "date-time",
3232
+ description: "Timestamp when position was recorded (ISO 8601)"
3233
+ },
3234
+ elevation_m: {
3235
+ type: "number",
3236
+ description: "Elevation/altitude above sea level (meters)"
3237
+ },
3238
+ accuracy_m: {
3239
+ type: "number",
3240
+ minimum: 0,
3241
+ description: "Horizontal accuracy/uncertainty (meters)"
3242
+ },
3243
+ speed_mps: {
3244
+ type: "number",
3245
+ minimum: 0,
3246
+ description: "Speed at this point (meters per second)"
3247
+ },
3248
+ heading_deg: {
3249
+ type: "number",
3250
+ minimum: 0,
3251
+ maximum: 360,
3252
+ description: "Heading/bearing (degrees from north, 0-360)"
3253
+ },
3254
+ heart_rate_bpm: {
3255
+ type: "integer",
3256
+ minimum: 0,
3257
+ description: "Heart rate at this position (bpm)"
3258
+ },
3259
+ power_watts: {
3260
+ type: "integer",
3261
+ minimum: 0,
3262
+ description: "Power at this position (watts)"
3263
+ },
3264
+ cadence: {
3265
+ type: "integer",
3266
+ minimum: 0,
3267
+ description: "Cadence at this position (RPM or SPM)"
3268
+ },
3269
+ temperature_c: {
3270
+ type: "number",
3271
+ description: "Temperature at this position (Celsius)"
3272
+ }
3273
+ }
3274
+ },
3275
+ GpsFix: {
3276
+ type: "string",
3277
+ "enum": [
3278
+ "none",
3279
+ "fix_2d",
3280
+ "fix_3d",
3281
+ "dgps",
3282
+ "unknown"
3283
+ ],
3284
+ description: "GPS fix quality indicator"
3285
+ },
3286
+ TimeSeriesData: {
3287
+ type: "object",
3288
+ required: [
3289
+ "timestamps"
3290
+ ],
3291
+ additionalProperties: false,
3292
+ description: "Columnar time-series data for second-by-second telemetry (PWF v2.1)",
3293
+ properties: {
3294
+ timestamps: {
3295
+ type: "array",
3296
+ items: {
3297
+ type: "string",
3298
+ format: "date-time"
3299
+ },
3300
+ description: "Timestamps for each record (ISO 8601). All other arrays must match this length."
3301
+ },
3302
+ elapsed_sec: {
3303
+ type: "array",
3304
+ items: {
3305
+ type: "integer",
3306
+ minimum: 0
3307
+ },
3308
+ description: "Elapsed time in seconds since start"
3309
+ },
3310
+ heart_rate: {
3311
+ type: "array",
3312
+ items: {
3313
+ type: "integer",
3314
+ minimum: 0
3315
+ },
3316
+ description: "Heart rate readings (bpm)"
3317
+ },
3318
+ power: {
3319
+ type: "array",
3320
+ items: {
3321
+ type: "integer",
3322
+ minimum: 0
3323
+ },
3324
+ description: "Power readings (watts)"
3325
+ },
3326
+ cadence: {
3327
+ type: "array",
3328
+ items: {
3329
+ type: "integer",
3330
+ minimum: 0
3331
+ },
3332
+ description: "Cadence readings (RPM for cycling, SPM for running/swimming)"
3333
+ },
3334
+ speed_mps: {
3335
+ type: "array",
3336
+ items: {
3337
+ type: "number",
3338
+ minimum: 0
3339
+ },
3340
+ description: "Speed readings (meters per second)"
3341
+ },
3342
+ distance_m: {
3343
+ type: "array",
3344
+ items: {
3345
+ type: "number",
3346
+ minimum: 0
3347
+ },
3348
+ description: "Distance readings (cumulative meters)"
3349
+ },
3350
+ elevation_m: {
3351
+ type: "array",
3352
+ items: {
3353
+ type: "number"
3354
+ },
3355
+ description: "Elevation/altitude readings (meters)"
3356
+ },
3357
+ temperature_c: {
3358
+ type: "array",
3359
+ items: {
3360
+ type: "number"
3361
+ },
3362
+ description: "Temperature readings (Celsius)"
3363
+ },
3364
+ latitude: {
3365
+ type: "array",
3366
+ items: {
3367
+ type: "number",
3368
+ minimum: -90,
3369
+ maximum: 90
3370
+ },
3371
+ description: "Latitude readings (decimal degrees)"
3372
+ },
3373
+ longitude: {
3374
+ type: "array",
3375
+ items: {
3376
+ type: "number",
3377
+ minimum: -180,
3378
+ maximum: 180
3379
+ },
3380
+ description: "Longitude readings (decimal degrees)"
3381
+ },
3382
+ grade_percent: {
3383
+ type: "array",
3384
+ items: {
3385
+ type: "number",
3386
+ minimum: -100,
3387
+ maximum: 100
3388
+ },
3389
+ description: "Grade/slope readings (percentage)"
3390
+ },
3391
+ respiration_rate: {
3392
+ type: "array",
3393
+ items: {
3394
+ type: "integer",
3395
+ minimum: 0
3396
+ },
3397
+ description: "Respiration rate (breaths per minute)"
3398
+ },
3399
+ core_temperature_c: {
3400
+ type: "array",
3401
+ items: {
3402
+ type: "number"
3403
+ },
3404
+ description: "Core body temperature (Celsius)"
3405
+ },
3406
+ muscle_oxygen_percent: {
3407
+ type: "array",
3408
+ items: {
3409
+ type: "number",
3410
+ minimum: 0,
3411
+ maximum: 100
3412
+ },
3413
+ description: "Muscle oxygen saturation (percentage)"
3414
+ },
3415
+ power_balance: {
3416
+ type: "array",
3417
+ items: {
3418
+ type: "number",
3419
+ minimum: 0,
3420
+ maximum: 100
3421
+ },
3422
+ description: "Left/right power balance (percentage left)"
3423
+ },
3424
+ left_pedal_smoothness: {
3425
+ type: "array",
3426
+ items: {
3427
+ type: "number",
3428
+ minimum: 0,
3429
+ maximum: 100
3430
+ },
3431
+ description: "Left pedal smoothness (percentage)"
3432
+ },
3433
+ right_pedal_smoothness: {
3434
+ type: "array",
3435
+ items: {
3436
+ type: "number",
3437
+ minimum: 0,
3438
+ maximum: 100
3439
+ },
3440
+ description: "Right pedal smoothness (percentage)"
3441
+ },
3442
+ left_torque_effectiveness: {
3443
+ type: "array",
3444
+ items: {
3445
+ type: "number",
3446
+ minimum: 0,
3447
+ maximum: 100
3448
+ },
3449
+ description: "Left torque effectiveness (percentage)"
3450
+ },
3451
+ right_torque_effectiveness: {
3452
+ type: "array",
3453
+ items: {
3454
+ type: "number",
3455
+ minimum: 0,
3456
+ maximum: 100
3457
+ },
3458
+ description: "Right torque effectiveness (percentage)"
3459
+ },
3460
+ stride_length_m: {
3461
+ type: "array",
3462
+ items: {
3463
+ type: "number",
3464
+ minimum: 0
3465
+ },
3466
+ description: "Running stride length (meters)"
3467
+ },
3468
+ vertical_oscillation_cm: {
3469
+ type: "array",
3470
+ items: {
3471
+ type: "number",
3472
+ minimum: 0
3473
+ },
3474
+ description: "Running vertical oscillation (centimeters)"
3475
+ },
3476
+ ground_contact_time_ms: {
3477
+ type: "array",
3478
+ items: {
3479
+ type: "integer",
3480
+ minimum: 0
3481
+ },
3482
+ description: "Running ground contact time (milliseconds)"
3483
+ },
3484
+ ground_contact_balance: {
3485
+ type: "array",
3486
+ items: {
3487
+ type: "number",
3488
+ minimum: 0,
3489
+ maximum: 100
3490
+ },
3491
+ description: "Running ground contact balance (percentage left)"
3492
+ },
3493
+ stroke_rate: {
3494
+ type: "array",
3495
+ items: {
3496
+ type: "integer",
3497
+ minimum: 0
3498
+ },
3499
+ description: "Swimming stroke rate (strokes per minute)"
3500
+ },
3501
+ stroke_count: {
3502
+ type: "array",
3503
+ items: {
3504
+ type: "integer",
3505
+ minimum: 0
3506
+ },
3507
+ description: "Swimming stroke count (cumulative)"
3508
+ },
3509
+ swolf: {
3510
+ type: "array",
3511
+ items: {
3512
+ type: "integer",
3513
+ minimum: 0
3514
+ },
3515
+ description: "Swimming SWOLF score"
3516
+ },
3517
+ stroke_type: {
3518
+ type: "array",
3519
+ items: {
3520
+ $ref: "#/$defs/StrokeType"
3521
+ },
3522
+ description: "Swimming stroke type at each point"
3523
+ }
3524
+ }
3525
+ },
3526
+ PoolConfig: {
3527
+ type: "object",
3528
+ required: [
3529
+ "pool_length"
3530
+ ],
3531
+ additionalProperties: false,
3532
+ description: "Pool configuration for swimming workouts (PWF v2.1)",
3533
+ properties: {
3534
+ pool_length: {
3535
+ type: "number",
3536
+ minimum: 0,
3537
+ description: "Length of the pool in the specified units"
3538
+ },
3539
+ pool_length_unit: {
3540
+ $ref: "#/$defs/PoolLengthUnit",
3541
+ description: "Unit for pool length (meters or yards)"
3542
+ }
3543
+ }
3544
+ },
3545
+ PoolLengthUnit: {
3546
+ type: "string",
3547
+ "enum": [
3548
+ "meters",
3549
+ "yards"
3550
+ ],
3551
+ "default": "meters",
3552
+ description: "Unit for pool length measurement"
3553
+ },
3554
+ SwimmingSetData: {
3555
+ type: "object",
3556
+ additionalProperties: false,
3557
+ description: "Swimming-specific data for a completed set (PWF v2.1)",
3558
+ properties: {
3559
+ lengths: {
3560
+ type: "array",
3561
+ items: {
3562
+ $ref: "#/$defs/SwimmingLength"
3563
+ },
3564
+ description: "Individual lengths within this set/lap"
3565
+ },
3566
+ stroke_type: {
3567
+ $ref: "#/$defs/StrokeType",
3568
+ description: "Primary stroke type for the set (if all lengths same stroke)"
3569
+ },
3570
+ total_lengths: {
3571
+ type: "integer",
3572
+ minimum: 0,
3573
+ description: "Total number of lengths in this set"
3574
+ },
3575
+ active_lengths: {
3576
+ type: "integer",
3577
+ minimum: 0,
3578
+ description: "Number of active lengths (excludes rest at wall)"
3579
+ },
3580
+ swolf_avg: {
3581
+ type: "integer",
3582
+ minimum: 0,
3583
+ description: "Average SWOLF across all lengths in this set"
3584
+ },
3585
+ drill_mode: {
3586
+ type: "boolean",
3587
+ description: "Whether this set was drill work (technique focus)"
3588
+ }
3589
+ }
3590
+ },
3591
+ SwimmingLength: {
3592
+ type: "object",
3593
+ required: [
3594
+ "length_number",
3595
+ "stroke_type",
3596
+ "duration_sec"
3597
+ ],
3598
+ additionalProperties: false,
3599
+ description: "A single length (one pool length) within a swimming set/lap (PWF v2.1)",
3600
+ properties: {
3601
+ length_number: {
3602
+ type: "integer",
3603
+ minimum: 1,
3604
+ description: "Length number within the set (1-indexed)"
3605
+ },
3606
+ stroke_type: {
3607
+ $ref: "#/$defs/StrokeType",
3608
+ description: "Stroke type used for this length"
3609
+ },
3610
+ duration_sec: {
3611
+ type: "integer",
3612
+ minimum: 0,
3613
+ description: "Duration of this length in seconds"
3614
+ },
3615
+ stroke_count: {
3616
+ type: "integer",
3617
+ minimum: 0,
3618
+ description: "Number of strokes taken during this length"
3619
+ },
3620
+ swolf: {
3621
+ type: "integer",
3622
+ minimum: 0,
3623
+ description: "SWOLF score (duration + stroke_count) - lower is better"
3624
+ },
3625
+ started_at: {
3626
+ type: "string",
3627
+ format: "date-time",
3628
+ description: "Timestamp when this length started (ISO 8601)"
3629
+ },
3630
+ active: {
3631
+ type: "boolean",
3632
+ description: "Whether this was an active length (vs. rest at wall)"
3633
+ }
3634
+ }
3635
+ },
3636
+ StrokeType: {
3637
+ type: "string",
3638
+ "enum": [
3639
+ "freestyle",
3640
+ "backstroke",
3641
+ "breaststroke",
3642
+ "butterfly",
3643
+ "drill",
3644
+ "mixed",
3645
+ "im"
3646
+ ],
3647
+ description: "Swimming stroke type for pool and open water swimming"
3648
+ },
3649
+ DeviceInfo: {
3650
+ type: "object",
3651
+ required: [
3652
+ "device_type",
3653
+ "manufacturer"
3654
+ ],
3655
+ additionalProperties: false,
3656
+ description: "Information about a device used during the workout (PWF v2)",
3657
+ properties: {
3658
+ device_index: {
3659
+ type: "integer",
3660
+ minimum: 0,
3661
+ maximum: 255,
3662
+ description: "Device index for multi-device workouts (e.g., 0=watch, 1=HRM, 2=power meter)"
3663
+ },
3664
+ device_type: {
3665
+ $ref: "#/$defs/DeviceType",
3666
+ description: "Type of device"
3667
+ },
3668
+ manufacturer: {
3669
+ type: "string",
3670
+ description: "Device manufacturer (can be a known manufacturer or custom string)"
3671
+ },
3672
+ product: {
3673
+ type: "string",
3674
+ description: "Specific product/model name"
3675
+ },
3676
+ serial_number: {
3677
+ type: "string",
3678
+ description: "Unique device serial number"
3679
+ },
3680
+ software_version: {
3681
+ type: "string",
3682
+ description: "Software/firmware version"
3683
+ },
3684
+ hardware_version: {
3685
+ type: "string",
3686
+ description: "Hardware version"
3687
+ },
3688
+ battery: {
3689
+ $ref: "#/$defs/BatteryInfo",
3690
+ description: "Battery information"
3691
+ },
3692
+ cumulative_operating_time_hours: {
3693
+ type: "number",
3694
+ minimum: 0,
3695
+ description: "Cumulative operating time in hours"
3696
+ },
3697
+ connection: {
3698
+ $ref: "#/$defs/ConnectionInfo",
3699
+ description: "Connection information for sensors"
3700
+ },
3701
+ calibration: {
3702
+ $ref: "#/$defs/CalibrationInfo",
3703
+ description: "Calibration information for sensors"
3704
+ }
3705
+ }
3706
+ },
3707
+ DeviceType: {
3708
+ type: "string",
3709
+ "enum": [
3710
+ "watch",
3711
+ "bike_computer",
3712
+ "heart_rate_monitor",
3713
+ "power_meter",
3714
+ "speed_sensor",
3715
+ "cadence_sensor",
3716
+ "speed_cadence_sensor",
3717
+ "foot_pod",
3718
+ "smart_trainer",
3719
+ "camera",
3720
+ "phone",
3721
+ "other"
3722
+ ],
3723
+ description: "Type of device"
3724
+ },
3725
+ BatteryInfo: {
3726
+ type: "object",
3727
+ additionalProperties: false,
3728
+ description: "Battery information for a device",
3729
+ properties: {
3730
+ start_percent: {
3731
+ type: "integer",
3732
+ minimum: 0,
3733
+ maximum: 100,
3734
+ description: "Battery level at start of workout (percentage)"
3735
+ },
3736
+ end_percent: {
3737
+ type: "integer",
3738
+ minimum: 0,
3739
+ maximum: 100,
3740
+ description: "Battery level at end of workout (percentage)"
3741
+ },
3742
+ voltage: {
3743
+ type: "number",
3744
+ minimum: 0,
3745
+ description: "Battery voltage"
3746
+ },
3747
+ status: {
3748
+ $ref: "#/$defs/BatteryStatus",
3749
+ description: "Battery status indicator"
3750
+ }
3751
+ }
3752
+ },
3753
+ BatteryStatus: {
3754
+ type: "string",
3755
+ "enum": [
3756
+ "good",
3757
+ "low",
3758
+ "critical",
3759
+ "charging",
3760
+ "unknown"
3761
+ ],
3762
+ description: "Battery status indicator"
3763
+ },
3764
+ ConnectionInfo: {
3765
+ type: "object",
3766
+ required: [
3767
+ "connection_type"
3768
+ ],
3769
+ additionalProperties: false,
3770
+ description: "Connection information for wireless sensors",
3771
+ properties: {
3772
+ connection_type: {
3773
+ $ref: "#/$defs/ConnectionType",
3774
+ description: "Type of connection"
3775
+ },
3776
+ ant_device_number: {
3777
+ type: "integer",
3778
+ minimum: 0,
3779
+ description: "ANT+ device number (for ANT+ sensors)"
3780
+ },
3781
+ bluetooth_id: {
3782
+ type: "string",
3783
+ description: "Bluetooth MAC address or identifier"
3784
+ }
3785
+ }
3786
+ },
3787
+ ConnectionType: {
3788
+ type: "string",
3789
+ "enum": [
3790
+ "local",
3791
+ "ant_plus",
3792
+ "bluetooth_le",
3793
+ "bluetooth",
3794
+ "wifi",
3795
+ "usb",
3796
+ "unknown"
3797
+ ],
3798
+ description: "Type of device connection"
3799
+ },
3800
+ CalibrationInfo: {
3801
+ type: "object",
3802
+ additionalProperties: false,
3803
+ description: "Calibration information for sensors (e.g., power meters)",
3804
+ properties: {
3805
+ calibration_factor: {
3806
+ type: "number",
3807
+ description: "Calibration factor or zero offset"
3808
+ },
3809
+ last_calibrated: {
3810
+ type: "string",
3811
+ format: "date-time",
3812
+ description: "Timestamp of last calibration"
3813
+ },
3814
+ auto_zero_enabled: {
3815
+ type: "boolean",
3816
+ description: "Auto-zero setting (for power meters)"
3817
+ }
3818
+ }
3819
+ }
3820
+ };
3821
+ var pwfHistoryV1 = {
3822
+ $schema: $schema,
3823
+ $id: $id,
3824
+ title: title,
3825
+ description: description,
3826
+ type: type,
3827
+ required: required,
3828
+ additionalProperties: additionalProperties,
3829
+ properties: properties,
3830
+ $defs: $defs
3831
+ };
3832
+
3833
+ export { type AdvancedMetrics, type AthleteProfile, type BatteryInfo, type BodyDimensions, type BodyMeasurement, type CalibrationInfo, type CompletedExercise, type CompletedSet, type ConnectionInfo, type Cycle, type Day, type DeviceInfo, type Exercise, type ExportSource, type GpsPosition, type GpsRoute, type HistoryExercise, type HistoryWorkout, type IntervalPhase, type LactateThreshold, type Meta, type Modality, type PWFHistoryExportV1, type PWFPlanV1, type ParseOptions, type PersonalRecord, PlanBuilder, type PlanCycle, type PlanDay, type PlanDocument, type PlanExercise, type PlanMeta, type PoolConfig, type PowerMetrics, type PwfHistory, type PwfPlan, type RampConfig, type SetTelemetry, type Severity, type SportSegment, type StrokeType, type SwimmingLength, type SwimmingSetData, type TimeInZones, type TimeSeriesData, type TrainingZone, type TransitionData, type Units, type Units1, type ValidationError, type ValidationIssue, type ValidationOptions, type ValidationWarning, type Workout, type WorkoutTelemetry, type WorkoutTelemetry1, fromYAML, pwfHistoryV1 as historySchema, isValidationIssueList, parseHistory, parsePlan, pwfV1 as planSchema, toYAML, validateHistory, validatePlan };