File size: 3,049 Bytes
35527e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import type { Activity } from '@/types';

// This file is deprecated - functionality moved to metricAcwr.ts
// Kept for reference only

interface ACWRData {
    dates: string[];
    acwr: (number | null)[];
    acuteLoad: (number | null)[];
    chronicLoad: (number | null)[];
}

/**
 * Calculate Acute-Chronic Workload Ratio (ACWR)
 * Acute load: 7-day rolling average
 * Chronic load: 28-day rolling average
 * ACWR = Acute / Chronic
 * Optimal range: 0.8 - 1.3
 */
export function calculateACWR(activities: Activity[], dateRange?: { start: Date; end: Date }): ACWRData {
    if (activities.length === 0 && !dateRange) {
        return { dates: [], acwr: [], acuteLoad: [], chronicLoad: [] };
    }

    const dates: string[] = [];
    const acwr: (number | null)[] = [];
    const acuteLoad: (number | null)[] = [];
    const chronicLoad: (number | null)[] = [];

    // Create a map of date to total load (using TSS, or distance as fallback)
    const dailyLoad = new Map<string, number>();

    activities.forEach(activity => {
        const dateKey = activity.date.toISOString().split('T')[0];
        const load = activity.trainingStressScore || activity.distance || 0;

        if (!dailyLoad.has(dateKey)) {
            dailyLoad.set(dateKey, 0);
        }
        dailyLoad.set(dateKey, dailyLoad.get(dateKey)! + load);
    });

    // Get all dates in range
    const startDate = dateRange?.start || (activities.length > 0 ? activities[0].date : new Date());
    const endDate = dateRange?.end || (activities.length > 0 ? activities[activities.length - 1].date : new Date());
    const allDates: Date[] = [];

    for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
        allDates.push(new Date(d));
    }

    // Calculate ACWR for each date
    allDates.forEach((date, index) => {
        const dateKey = date.toISOString().split('T')[0];
        dates.push(dateKey);

        // Need at least 28 days of data for chronic load
        if (index < 27) {
            acuteLoad.push(null);
            chronicLoad.push(null);
            acwr.push(null);
            return;
        }

        // Calculate acute load (7-day average)
        let acuteSum = 0;
        for (let i = 0; i < 7; i++) {
            const d = allDates[index - i];
            const key = d.toISOString().split('T')[0];
            acuteSum += dailyLoad.get(key) || 0;
        }
        const acuteAvg = acuteSum / 7;

        // Calculate chronic load (28-day average)
        let chronicSum = 0;
        for (let i = 0; i < 28; i++) {
            const d = allDates[index - i];
            const key = d.toISOString().split('T')[0];
            chronicSum += dailyLoad.get(key) || 0;
        }
        const chronicAvg = chronicSum / 28;

        acuteLoad.push(acuteAvg);
        chronicLoad.push(chronicAvg);

        // Calculate ACWR
        if (chronicAvg > 0) {
            acwr.push(acuteAvg / chronicAvg);
        } else {
            acwr.push(null);
        }
    });

    return { dates, acwr, acuteLoad, chronicLoad };
}