@dofek/training 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/dist/activity-icons.d.ts +10 -0
  4. package/dist/activity-icons.d.ts.map +1 -0
  5. package/dist/activity-icons.js +102 -0
  6. package/dist/activity-icons.js.map +1 -0
  7. package/dist/climbing-grades.d.ts +8 -0
  8. package/dist/climbing-grades.d.ts.map +1 -0
  9. package/dist/climbing-grades.js +80 -0
  10. package/dist/climbing-grades.js.map +1 -0
  11. package/dist/derived-cardio.d.ts +17 -0
  12. package/dist/derived-cardio.d.ts.map +1 -0
  13. package/dist/derived-cardio.js +84 -0
  14. package/dist/derived-cardio.js.map +1 -0
  15. package/dist/endurance-types.d.ts +21 -0
  16. package/dist/endurance-types.d.ts.map +1 -0
  17. package/dist/endurance-types.js +39 -0
  18. package/dist/endurance-types.js.map +1 -0
  19. package/dist/grade-adjusted-pace.d.ts +34 -0
  20. package/dist/grade-adjusted-pace.d.ts.map +1 -0
  21. package/dist/grade-adjusted-pace.js +41 -0
  22. package/dist/grade-adjusted-pace.js.map +1 -0
  23. package/dist/muscle-groups.d.ts +35 -0
  24. package/dist/muscle-groups.d.ts.map +1 -0
  25. package/dist/muscle-groups.js +135 -0
  26. package/dist/muscle-groups.js.map +1 -0
  27. package/dist/pmc.d.ts +77 -0
  28. package/dist/pmc.d.ts.map +1 -0
  29. package/dist/pmc.js +83 -0
  30. package/dist/pmc.js.map +1 -0
  31. package/dist/power-analysis.d.ts +82 -0
  32. package/dist/power-analysis.d.ts.map +1 -0
  33. package/dist/power-analysis.js +162 -0
  34. package/dist/power-analysis.js.map +1 -0
  35. package/dist/training-distribution.d.ts +43 -0
  36. package/dist/training-distribution.d.ts.map +1 -0
  37. package/dist/training-distribution.js +68 -0
  38. package/dist/training-distribution.js.map +1 -0
  39. package/dist/training-load.d.ts +66 -0
  40. package/dist/training-load.d.ts.map +1 -0
  41. package/dist/training-load.js +89 -0
  42. package/dist/training-load.js.map +1 -0
  43. package/dist/training.d.ts +61 -0
  44. package/dist/training.d.ts.map +1 -0
  45. package/dist/training.js +564 -0
  46. package/dist/training.js.map +1 -0
  47. package/dist/workout-recommendation.d.ts +86 -0
  48. package/dist/workout-recommendation.d.ts.map +1 -0
  49. package/dist/workout-recommendation.js +338 -0
  50. package/dist/workout-recommendation.js.map +1 -0
  51. package/package.json +52 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"training-distribution.d.ts","sourceRoot":"","sources":["../src/training-distribution.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,oBAAoB,CAAC;IAC5B,aAAa,EAAE,WAAW,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,yBAAyB,EAAE,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,eAAe,GAAG,mBAAmB,CAAC;AAErF,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,qBAAsB,SAAQ,0BAA0B;IACvE,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE;QACf,EAAE,EAAE,MAAM,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;KACZ,CAAC;IACF,MAAM,EAAE,kBAAkB,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AASD,wBAAgB,kCAAkC,CAChD,KAAK,EAAE,gBAAgB,EAAE,GACxB,qBAAqB,CA0BvB;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,0BAA0B,GAChC,qBAAqB,CAuCvB"}
@@ -0,0 +1,68 @@
1
+ import { HEART_RATE_ZONES } from "@dofek/zones/zones";
2
+ const INTENSITY_EXPLANATION = "A descriptive view of endurance training time across the Karvonen five-zone heart-rate model. It does not classify training polarization.";
3
+ function roundedPercent(seconds, totalSeconds) {
4
+ return totalSeconds > 0 ? Math.round((seconds / totalSeconds) * 1000) / 10 : 0;
5
+ }
6
+ export function buildKarvonenIntensityDistribution(weeks) {
7
+ const zoneSeconds = [
8
+ weeks.reduce((sum, week) => sum + week.zone0, 0),
9
+ weeks.reduce((sum, week) => sum + week.zone1, 0),
10
+ weeks.reduce((sum, week) => sum + week.zone2, 0),
11
+ weeks.reduce((sum, week) => sum + week.zone3, 0),
12
+ weeks.reduce((sum, week) => sum + week.zone4, 0),
13
+ weeks.reduce((sum, week) => sum + week.zone5, 0),
14
+ ];
15
+ const totalSeconds = zoneSeconds.reduce((sum, seconds) => sum + seconds, 0);
16
+ return {
17
+ model: "karvonen-five-zone",
18
+ activityScope: "endurance",
19
+ totalSeconds,
20
+ zones: HEART_RATE_ZONES.map((zoneDefinition) => {
21
+ const seconds = zoneSeconds[zoneDefinition.zone] ?? 0;
22
+ return {
23
+ zone: zoneDefinition.zone,
24
+ label: zoneDefinition.label,
25
+ seconds,
26
+ percent: roundedPercent(seconds, totalSeconds),
27
+ };
28
+ }),
29
+ explanation: INTENSITY_EXPLANATION,
30
+ };
31
+ }
32
+ export function buildTreffPolarizationWeek(input) {
33
+ const totalSeconds = input.z1Seconds + input.z2Seconds + input.z3Seconds;
34
+ const zonePercentages = {
35
+ z1: roundedPercent(input.z1Seconds, totalSeconds),
36
+ z2: roundedPercent(input.z2Seconds, totalSeconds),
37
+ z3: roundedPercent(input.z3Seconds, totalSeconds),
38
+ };
39
+ if (input.polarizationIndex === null) {
40
+ return {
41
+ ...input,
42
+ totalSeconds,
43
+ zonePercentages,
44
+ status: "insufficient_data",
45
+ statusLabel: "Insufficient data",
46
+ explanation: "Polarization needs recorded cycling time in all three Treff heart-rate zones.",
47
+ };
48
+ }
49
+ if (input.polarizationIndex > 2) {
50
+ return {
51
+ ...input,
52
+ totalSeconds,
53
+ zonePercentages,
54
+ status: "polarized",
55
+ statusLabel: "Polarized",
56
+ explanation: "This cycling week is polarized: it has a Treff polarization index above 2.00.",
57
+ };
58
+ }
59
+ return {
60
+ ...input,
61
+ totalSeconds,
62
+ zonePercentages,
63
+ status: "not_polarized",
64
+ statusLabel: "Not polarized",
65
+ explanation: "This cycling week is not polarized because its Treff polarization index is 2.00 or lower.",
66
+ };
67
+ }
68
+ //# sourceMappingURL=training-distribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"training-distribution.js","sourceRoot":"","sources":["../src/training-distribution.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Training stress calculator.
3
+ *
4
+ * Encapsulates Bannister TRIMP, hrTSS, power TSS, regression model building,
5
+ * and FTP estimation. Instance methods use personalized gender factor and
6
+ * exponent; static methods are parameter-independent.
7
+ */
8
+ export interface ActivityRow {
9
+ id: string;
10
+ date: string;
11
+ duration_min: number;
12
+ avg_hr: number;
13
+ max_hr: number;
14
+ avg_power: number | null;
15
+ power_samples: number;
16
+ hr_samples: number;
17
+ }
18
+ export interface TssModelInfo {
19
+ type: "learned" | "generic";
20
+ pairedActivities: number;
21
+ r2: number | null;
22
+ ftp: number | null;
23
+ }
24
+ export declare class TrainingStressCalculator {
25
+ #private;
26
+ constructor(genderFactor?: number, exponent?: number);
27
+ /**
28
+ * Compute Bannister TRIMP for an activity.
29
+ *
30
+ * TRIMP = duration_minutes * deltaHR_ratio * genderFactor * e^(exponent * deltaHR_ratio)
31
+ * where deltaHR_ratio = (avg_hr - resting_hr) / (max_hr - resting_hr)
32
+ */
33
+ computeTrimp(durationMin: number, avgHr: number, maxHr: number, restingHr: number): number;
34
+ /**
35
+ * Compute hrTSS using Bannister TRIMP normalized to 1hr at threshold.
36
+ * This is the fallback when no learned model is available.
37
+ */
38
+ computeHrTss(durationMin: number, avgHr: number, maxHr: number, restingHr: number): number;
39
+ /**
40
+ * Compute power-based TSS.
41
+ * TSS = (NP / FTP)^2 * duration_hours * 100
42
+ */
43
+ static computePowerTss(normalizedPower: number, ftp: number, durationMin: number): number;
44
+ /**
45
+ * Build a linear regression model: powerTss = slope * trimp + intercept.
46
+ * Returns null if insufficient data or poor fit.
47
+ */
48
+ static buildTssModel(paired: {
49
+ trimp: number;
50
+ powerTss: number;
51
+ }[]): {
52
+ slope: number;
53
+ intercept: number;
54
+ r2: number;
55
+ } | null;
56
+ /**
57
+ * Estimate FTP from activity data.
58
+ * Uses highest avg_power from activities >= 20 min duration, multiplied by 0.95.
59
+ *
60
+ * Intentionally uses avg_power rather than Normalized Power (NP).
61
+ * NP inflates power for interval workouts via 4th-power averaging,
62
+ * which produces unrealistically high FTP estimates from variable efforts.
63
+ */
64
+ static estimateFtp(activities: ActivityRow[]): number | null;
65
+ }
66
+ //# sourceMappingURL=training-load.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"training-load.d.ts","sourceRoot":"","sources":["../src/training-load.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAID,qBAAa,wBAAwB;;gBAIvB,YAAY,SAAO,EAAE,QAAQ,SAAO;IAKhD;;;;;OAKG;IACH,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAS1F;;;OAGG;IACH,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAgB1F;;;OAGG;IACH,MAAM,CAAC,eAAe,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM;IAMzF;;;OAGG;IACH,MAAM,CAAC,aAAa,CAClB,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,GAC5C;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAa1D;;;;;;;OAOG;IACH,MAAM,CAAC,WAAW,CAAC,UAAU,EAAE,WAAW,EAAE,GAAG,MAAM,GAAG,IAAI;CAQ7D"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Training stress calculator.
3
+ *
4
+ * Encapsulates Bannister TRIMP, hrTSS, power TSS, regression model building,
5
+ * and FTP estimation. Instance methods use personalized gender factor and
6
+ * exponent; static methods are parameter-independent.
7
+ */
8
+ import { linearRegression } from "./power-analysis.js";
9
+ // ── TrainingStressCalculator ─────────────────────────────────────────
10
+ export class TrainingStressCalculator {
11
+ #genderFactor;
12
+ #exponent;
13
+ constructor(genderFactor = 0.64, exponent = 1.92) {
14
+ this.#genderFactor = genderFactor;
15
+ this.#exponent = exponent;
16
+ }
17
+ /**
18
+ * Compute Bannister TRIMP for an activity.
19
+ *
20
+ * TRIMP = duration_minutes * deltaHR_ratio * genderFactor * e^(exponent * deltaHR_ratio)
21
+ * where deltaHR_ratio = (avg_hr - resting_hr) / (max_hr - resting_hr)
22
+ */
23
+ computeTrimp(durationMin, avgHr, maxHr, restingHr) {
24
+ if (maxHr <= restingHr || durationMin <= 0)
25
+ return 0;
26
+ const deltaHrRatio = (avgHr - restingHr) / (maxHr - restingHr);
27
+ if (deltaHrRatio <= 0)
28
+ return 0;
29
+ return (durationMin * deltaHrRatio * this.#genderFactor * Math.exp(this.#exponent * deltaHrRatio));
30
+ }
31
+ /**
32
+ * Compute hrTSS using Bannister TRIMP normalized to 1hr at threshold.
33
+ * This is the fallback when no learned model is available.
34
+ */
35
+ computeHrTss(durationMin, avgHr, maxHr, restingHr) {
36
+ const trimp = this.computeTrimp(durationMin, avgHr, maxHr, restingHr);
37
+ if (trimp === 0)
38
+ return 0;
39
+ // Threshold HR at 85% of max HR
40
+ const thresholdDeltaRatio = 0.85;
41
+ const trimpOneHourAtThreshold = 60 *
42
+ thresholdDeltaRatio *
43
+ this.#genderFactor *
44
+ Math.exp(this.#exponent * thresholdDeltaRatio);
45
+ if (trimpOneHourAtThreshold === 0)
46
+ return 0;
47
+ return (trimp / trimpOneHourAtThreshold) * 100;
48
+ }
49
+ /**
50
+ * Compute power-based TSS.
51
+ * TSS = (NP / FTP)^2 * duration_hours * 100
52
+ */
53
+ static computePowerTss(normalizedPower, ftp, durationMin) {
54
+ if (ftp <= 0 || durationMin <= 0 || normalizedPower <= 0)
55
+ return 0;
56
+ const intensityFactor = normalizedPower / ftp;
57
+ return intensityFactor ** 2 * (durationMin / 60) * 100;
58
+ }
59
+ /**
60
+ * Build a linear regression model: powerTss = slope * trimp + intercept.
61
+ * Returns null if insufficient data or poor fit.
62
+ */
63
+ static buildTssModel(paired) {
64
+ if (paired.length < 10)
65
+ return null;
66
+ const xs = paired.map((point) => point.trimp);
67
+ const ys = paired.map((point) => point.powerTss);
68
+ const result = linearRegression(xs, ys);
69
+ if (result.r2 < 0.3 || result.slope <= 0)
70
+ return null;
71
+ return result;
72
+ }
73
+ /**
74
+ * Estimate FTP from activity data.
75
+ * Uses highest avg_power from activities >= 20 min duration, multiplied by 0.95.
76
+ *
77
+ * Intentionally uses avg_power rather than Normalized Power (NP).
78
+ * NP inflates power for interval workouts via 4th-power averaging,
79
+ * which produces unrealistically high FTP estimates from variable efforts.
80
+ */
81
+ static estimateFtp(activities) {
82
+ const qualifying = activities.filter((act) => act.avg_power != null && act.avg_power > 0 && act.duration_min >= 20);
83
+ if (qualifying.length === 0)
84
+ return null;
85
+ const bestPower = Math.max(...qualifying.map((act) => Number(act.avg_power)));
86
+ return Math.round(bestPower * 0.95);
87
+ }
88
+ }
89
+ //# sourceMappingURL=training-load.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"training-load.js","sourceRoot":"","sources":["../src/training-load.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ export declare const OTHER_ACTIVITY_TYPE = "__other__";
2
+ /**
3
+ * Superset of all valid internal activity types across all providers.
4
+ * Providers map their domain-specific types to one of these canonical values.
5
+ */
6
+ export declare const CANONICAL_ACTIVITY_TYPES: readonly ["cycling", "road_cycling", "mountain_biking", "gravel_cycling", "indoor_cycling", "virtual_cycling", "e_bike_cycling", "cyclocross", "track_cycling", "bmx", "running", "swimming", "walking", "hiking", "strength", "strength_training", "functional_strength", "yoga", "pilates", "elliptical", "rowing", "skiing", "skating", "climbing", "cardio", "hiit", "cross_training", "trail_running", "stair_climbing", "stairmaster", "stretching", "dance", "martial_arts", "tennis", "basketball", "soccer", "golf", "ice_hockey", "snowboarding", "rock_climbing", "surfing", "kayaking", "functional_fitness", "bootcamp", "boxing", "core", "aqua_fitness", "circuit_training", "group_exercise", "triathlon", "dancing", "american_football", "archery", "australian_football", "badminton", "baseball", "bowling", "cricket", "curling", "equestrian", "fencing", "fishing", "gymnastics", "handball", "hockey", "hunting", "lacrosse", "mind_and_body", "mixed_cardio", "paddle_sports", "play", "preparation_and_recovery", "racquetball", "rugby", "sailing", "snow_sports", "softball", "squash", "table_tennis", "track_and_field", "volleyball", "water_fitness", "water_polo", "water_sports", "wrestling", "barre", "core_training", "flexibility", "jump_rope", "kickboxing", "stairs", "step_training", "wheelchair_walk", "wheelchair_run", "tai_chi", "mixed_metabolic_cardio", "hand_cycling", "disc_sports", "fitness_gaming", "cardio_dance", "social_dance", "paddle_racquet", "cooldown", "transition", "underwater_diving", "cross_country_skiing", "downhill_skiing", "breathwork", "meditation", "paddleboarding", "snowshoeing", "pickleball", "padel", "football", "multisport", "disc_golf", "skydiving", "paragliding", "diving", "snorkeling", "navigation", "geocaching", "paddling", "gym", "open_water_swimming", "other"];
7
+ export type CanonicalActivityType = (typeof CANONICAL_ACTIVITY_TYPES)[number];
8
+ /**
9
+ * All canonical activity types considered "cycling" for consolidated views.
10
+ * Individual activities show the specific subtype, but aggregated views
11
+ * (weekly volume, PMC, cycling page) treat them as one sport.
12
+ */
13
+ export declare const CYCLING_ACTIVITY_TYPES: readonly ["cycling", "road_cycling", "mountain_biking", "gravel_cycling", "indoor_cycling", "virtual_cycling", "e_bike_cycling", "cyclocross", "track_cycling", "bmx", "hand_cycling"];
14
+ export type CyclingActivityType = (typeof CYCLING_ACTIVITY_TYPES)[number];
15
+ /** Check whether an activity type is a cycling variant. */
16
+ export declare function isCyclingActivity(activityType: string): activityType is CyclingActivityType;
17
+ /** Cadence unit for display based on activity type. */
18
+ export declare function cadenceUnit(activityType: string): "rpm" | "steps/min";
19
+ /** Y-axis label for cadence charts. */
20
+ export declare function cadenceAxisLabel(activityType: string): string;
21
+ /**
22
+ * Creates a mapping function from provider-specific types to canonical types.
23
+ * Unknown provider types default to "other".
24
+ */
25
+ export declare function createActivityTypeMapper<K extends string | number>(providerMappings: Record<K, CanonicalActivityType>): (providerType: K) => CanonicalActivityType;
26
+ /** Strava sport_type → canonical activity type */
27
+ export declare const STRAVA_ACTIVITY_TYPE_MAP: Record<string, CanonicalActivityType>;
28
+ /** Wahoo workout_type_id (numeric) → canonical activity type */
29
+ export declare const WAHOO_WORKOUT_TYPE_MAP: Record<number, CanonicalActivityType>;
30
+ /** Polar sport (lowercased) → canonical activity type */
31
+ export declare const POLAR_SPORT_MAP: Record<string, CanonicalActivityType>;
32
+ /** Garmin activityType → canonical activity type */
33
+ export declare const GARMIN_ACTIVITY_TYPE_MAP: Record<string, CanonicalActivityType>;
34
+ /** Oura activity (lowercased) → canonical activity type */
35
+ export declare const OURA_ACTIVITY_TYPE_MAP: Record<string, CanonicalActivityType>;
36
+ /** RideWithGPS activity_type → canonical activity type */
37
+ export declare const RIDE_WITH_GPS_ACTIVITY_TYPE_MAP: Record<string, CanonicalActivityType>;
38
+ /** Apple Health HKWorkoutActivityType → normalized lowercase name */
39
+ export declare const APPLE_HEALTH_WORKOUT_TYPE_MAP: Record<string, CanonicalActivityType>;
40
+ export interface WeeklyVolumeChartRow {
41
+ week: string;
42
+ activity_type: string;
43
+ count: number;
44
+ hours: number;
45
+ }
46
+ export interface DailyLoadRow {
47
+ date: string;
48
+ dailyLoad: number;
49
+ }
50
+ export type VerticalAscentActivityTypeGroup = "road_cycling" | "mountain_biking" | "gravel_cycling" | "other_cycling";
51
+ export declare function formatActivityTypeLabel(activityType: string): string;
52
+ export declare function getVerticalAscentActivityTypeGroup(activityType: string): VerticalAscentActivityTypeGroup;
53
+ export declare function formatVerticalAscentActivityTypeGroupLabel(activityTypeGroup: VerticalAscentActivityTypeGroup): string;
54
+ export declare function collapseWeeklyVolumeActivityTypes(rows: WeeklyVolumeChartRow[], maxLegendItems?: number): WeeklyVolumeChartRow[];
55
+ /**
56
+ * Pick the daily load row to display in "current strain" UI.
57
+ * Always returns the latest (most recent) row so the dashboard reflects the
58
+ * current day's rolling workload state.
59
+ */
60
+ export declare function selectRecentDailyLoad<T extends DailyLoadRow>(rows: T[]): T | null;
61
+ //# sourceMappingURL=training.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"training.d.ts","sourceRoot":"","sources":["../src/training.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAM/C;;;GAGG;AACH,eAAO,MAAM,wBAAwB,swDAiI3B,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAM9E;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,wLAYzB,CAAC;AAEX,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1E,2DAA2D;AAC3D,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,YAAY,IAAI,mBAAmB,CAE3F;AAED,uDAAuD;AACvD,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,KAAK,GAAG,WAAW,CAErE;AAED,uCAAuC;AACvC,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE7D;AAUD;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAChE,gBAAgB,EAAE,MAAM,CAAC,CAAC,EAAE,qBAAqB,CAAC,GACjD,CAAC,YAAY,EAAE,CAAC,KAAK,qBAAqB,CAI5C;AAMD,kDAAkD;AAClD,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CA2B1E,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAexE,CAAC;AAEF,yDAAyD;AACzD,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CA2CjE,CAAC;AAEF,oDAAoD;AACpD,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CA4B1E,CAAC;AAEF,2DAA2D;AAC3D,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAgBxE,CAAC;AAEF,0DAA0D;AAC1D,eAAO,MAAM,+BAA+B,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAkBjF,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAoF/E,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAgCD,MAAM,MAAM,+BAA+B,GACvC,cAAc,GACd,iBAAiB,GACjB,gBAAgB,GAChB,eAAe,CAAC;AA8BpB,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAMpE;AAED,wBAAgB,kCAAkC,CAChD,YAAY,EAAE,MAAM,GACnB,+BAA+B,CAMjC;AAED,wBAAgB,0CAA0C,CACxD,iBAAiB,EAAE,+BAA+B,GACjD,MAAM,CAER;AAED,wBAAgB,iCAAiC,CAC/C,IAAI,EAAE,oBAAoB,EAAE,EAC5B,cAAc,SAAI,GACjB,oBAAoB,EAAE,CAgDxB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,YAAY,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,GAAG,IAAI,CAGjF"}